Data Pre-processing

Load needed libraries

library(fastDummies)
library(readr)
library(ggplot2)
library(dplyr)
library(caret)
library(glmnet)
library(boot)
library(tree)
library(ranger)
library(xgboost)
library(gbm)
library(vip)
library(ISLR)
library(tidyr)

Set the seed for reproducibility

set.seed(1)

Load the dataset

original_lc_data <- read.csv("LCdata.csv",sep = ";")
lc_data <- original_lc_data

remove attributes not available for prediction

lc_data <- subset(lc_data, select = -c(collection_recovery_fee, installment, issue_d,
                                       last_pymnt_amnt, last_pymnt_d, loan_status,
                                       next_pymnt_d, out_prncp, out_prncp_inv,
                                       pymnt_plan, recoveries, total_pymnt,
                                       total_pymnt_inv,total_rec_int, total_rec_late_fee,                                                  total_rec_prncp))
summary(lc_data)
       id             member_id          loan_amnt      funded_amnt   
 Min.   :   54734   Min.   :   70473   Min.   :  500   Min.   :  500  
 1st Qu.: 9207230   1st Qu.:10877939   1st Qu.: 8000   1st Qu.: 8000  
 Median :34433372   Median :37095300   Median :13000   Median :13000  
 Mean   :32463636   Mean   :35000265   Mean   :14754   Mean   :14741  
 3rd Qu.:54900100   3rd Qu.:58470266   3rd Qu.:20000   3rd Qu.:20000  
 Max.   :68617057   Max.   :73544841   Max.   :35000   Max.   :35000  
                                                                      
 funded_amnt_inv     term              int_rate      emp_title        
 Min.   :    0   Length:798641      Min.   : 5.32   Length:798641     
 1st Qu.: 8000   Class :character   1st Qu.: 9.99   Class :character  
 Median :13000   Mode  :character   Median :12.99   Mode  :character  
 Mean   :14702                      Mean   :13.24                     
 3rd Qu.:20000                      3rd Qu.:16.20                     
 Max.   :35000                      Max.   :28.99                     
                                                                      
  emp_length        home_ownership       annual_inc      verification_status
 Length:798641      Length:798641      Min.   :      0   Length:798641      
 Class :character   Class :character   1st Qu.:  45000   Class :character   
 Mode  :character   Mode  :character   Median :  65000   Mode  :character   
                                       Mean   :  75014                      
                                       3rd Qu.:  90000                      
                                       Max.   :9500000                      
                                       NA's   :4                            
     url                desc             purpose             title          
 Length:798641      Length:798641      Length:798641      Length:798641     
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
   zip_code          addr_state             dti           delinq_2yrs     
 Length:798641      Length:798641      Min.   :   0.00   Min.   : 0.0000  
 Class :character   Class :character   1st Qu.:  11.91   1st Qu.: 0.0000  
 Mode  :character   Mode  :character   Median :  17.66   Median : 0.0000  
                                       Mean   :  18.16   Mean   : 0.3145  
                                       3rd Qu.:  23.95   3rd Qu.: 0.0000  
                                       Max.   :9999.00   Max.   :39.0000  
                                                         NA's   :25       
 earliest_cr_line   inq_last_6mths    mths_since_last_delinq
 Length:798641      Min.   : 0.0000   Min.   :  0.0         
 Class :character   1st Qu.: 0.0000   1st Qu.: 15.0         
 Mode  :character   Median : 0.0000   Median : 31.0         
                    Mean   : 0.6947   Mean   : 34.1         
                    3rd Qu.: 1.0000   3rd Qu.: 50.0         
                    Max.   :33.0000   Max.   :188.0         
                    NA's   :25        NA's   :408818        
 mths_since_last_record    open_acc        pub_rec          revol_bal      
 Min.   :  0.0          Min.   : 0.00   Min.   : 0.0000   Min.   :      0  
 1st Qu.: 51.0          1st Qu.: 8.00   1st Qu.: 0.0000   1st Qu.:   6443  
 Median : 70.0          Median :11.00   Median : 0.0000   Median :  11876  
 Mean   : 70.1          Mean   :11.55   Mean   : 0.1953   Mean   :  16930  
 3rd Qu.: 92.0          3rd Qu.:14.00   3rd Qu.: 0.0000   3rd Qu.:  20839  
 Max.   :129.0          Max.   :90.00   Max.   :63.0000   Max.   :2904836  
 NA's   :675190         NA's   :25      NA's   :25        NA's   :2        
   revol_util       total_acc      initial_list_status last_credit_pull_d
 Min.   :  0.00   Min.   :  1.00   Length:798641       Length:798641     
 1st Qu.: 37.70   1st Qu.: 17.00   Class :character    Class :character  
 Median : 56.00   Median : 24.00   Mode  :character    Mode  :character  
 Mean   : 55.05   Mean   : 25.27                                         
 3rd Qu.: 73.50   3rd Qu.: 32.00                                         
 Max.   :892.30   Max.   :169.00                                         
 NA's   :454      NA's   :25                                             
 collections_12_mths_ex_med mths_since_last_major_derog  policy_code
 Min.   : 0.00000           Min.   :  0.0               Min.   :1   
 1st Qu.: 0.00000           1st Qu.: 27.0               1st Qu.:1   
 Median : 0.00000           Median : 44.0               Median :1   
 Mean   : 0.01447           Mean   : 44.1               Mean   :1   
 3rd Qu.: 0.00000           3rd Qu.: 61.0               3rd Qu.:1   
 Max.   :20.00000           Max.   :188.0               Max.   :1   
 NA's   :126                NA's   :599107                          
 application_type   annual_inc_joint   dti_joint     
 Length:798641      Min.   : 17950   Min.   : 3.0    
 Class :character   1st Qu.: 76167   1st Qu.:13.3    
 Mode  :character   Median :101886   Median :17.7    
                    Mean   :110745   Mean   :18.4    
                    3rd Qu.:133000   3rd Qu.:22.6    
                    Max.   :500000   Max.   :43.9    
                    NA's   :798181   NA's   :798183  
 verification_status_joint acc_now_delinq       tot_coll_amt    
 Length:798641             Min.   : 0.000000   Min.   :      0  
 Class :character          1st Qu.: 0.000000   1st Qu.:      0  
 Mode  :character          Median : 0.000000   Median :      0  
                           Mean   : 0.005026   Mean   :    228  
                           3rd Qu.: 0.000000   3rd Qu.:      0  
                           Max.   :14.000000   Max.   :9152545  
                           NA's   :25          NA's   :63276    
  tot_cur_bal       open_acc_6m       open_il_6m      open_il_12m    
 Min.   :      0   Min.   : 0.0     Min.   : 0.0     Min.   : 0.0    
 1st Qu.:  29861   1st Qu.: 0.0     1st Qu.: 1.0     1st Qu.: 0.0    
 Median :  80647   Median : 1.0     Median : 2.0     Median : 0.0    
 Mean   : 139508   Mean   : 1.1     Mean   : 2.9     Mean   : 0.8    
 3rd Qu.: 208229   3rd Qu.: 2.0     3rd Qu.: 4.0     3rd Qu.: 1.0    
 Max.   :8000078   Max.   :14.0     Max.   :33.0     Max.   :12.0    
 NA's   :63276     NA's   :779525   NA's   :779525   NA's   :779525  
  open_il_24m     mths_since_rcnt_il  total_bal_il       il_util      
 Min.   : 0.0     Min.   :  0.0      Min.   :     0   Min.   :  0.0   
 1st Qu.: 0.0     1st Qu.:  6.0      1st Qu.: 10164   1st Qu.: 58.4   
 Median : 1.0     Median : 12.0      Median : 24544   Median : 74.8   
 Mean   : 1.7     Mean   : 21.1      Mean   : 36428   Mean   : 71.5   
 3rd Qu.: 2.0     3rd Qu.: 23.0      3rd Qu.: 47640   3rd Qu.: 87.7   
 Max.   :19.0     Max.   :363.0      Max.   :878459   Max.   :223.3   
 NA's   :779525   NA's   :780030     NA's   :779525   NA's   :782007  
  open_rv_12m      open_rv_24m       max_bal_bc        all_util     
 Min.   : 0.0     Min.   : 0       Min.   :    0    Min.   :  0.0   
 1st Qu.: 0.0     1st Qu.: 1       1st Qu.: 2406    1st Qu.: 47.6   
 Median : 1.0     Median : 2       Median : 4502    Median : 61.9   
 Mean   : 1.4     Mean   : 3       Mean   : 5878    Mean   : 60.8   
 3rd Qu.: 2.0     3rd Qu.: 4       3rd Qu.: 7774    3rd Qu.: 75.2   
 Max.   :22.0     Max.   :43       Max.   :83047    Max.   :151.4   
 NA's   :779525   NA's   :779525   NA's   :779525   NA's   :779525  
 total_rev_hi_lim      inq_fi        total_cu_tl      inq_last_12m   
 Min.   :      0   Min.   : 0.0     Min.   : 0.0     Min.   :-4      
 1st Qu.:  13900   1st Qu.: 0.0     1st Qu.: 0.0     1st Qu.: 0      
 Median :  23700   Median : 0.0     Median : 0.0     Median : 2      
 Mean   :  32093   Mean   : 0.9     Mean   : 1.5     Mean   : 2      
 3rd Qu.:  39800   3rd Qu.: 1.0     3rd Qu.: 2.0     3rd Qu.: 3      
 Max.   :9999999   Max.   :16.0     Max.   :35.0     Max.   :32      
 NA's   :63276     NA's   :779525   NA's   :779525   NA's   :779525  

First we delete the columns which aren’t useful for our prediction

lc_data$id <- NULL
lc_data$member_id <- NULL
lc_data$zip_code <- NULL
lc_data$url <- NULL

Looks like policy_code contains just value equal to 1, it can be removed

lc_data$policy_code <- NULL

Remove additional columns which are related to the historical data

lc_data$last_credit_pull_d <- NULL

Then we delete the columns which can’t be converted to categorical and require NLP

lc_data$title <- NULL
lc_data$desc <- NULL
lc_data$emp_title <- NULL

let’s examine the loan_amnt column

sum(is.na(lc_data$loan_amnt))
[1] 0
cor(lc_data$loan_amnt, lc_data$int_rate)
[1] 0.1447189
hist(lc_data$loan_amnt, breaks = 20, main = "loan_amnt distribution", xlab = "loan_amnt", col = "lightblue", border = "black")

ggplot(data = lc_data, mapping = aes(x=int_rate,y=loan_amnt)) + geom_boxplot()

standardize loan_amnt

#lc_data$loan_amnt <- scale(lc_data$loan_amnt)

let’s examine the funded_amnt column

sum(is.na(lc_data$funded_amnt))
[1] 0
cor(lc_data$funded_amnt, lc_data$int_rate)
[1] 0.1448634
hist(lc_data$funded_amnt, breaks = 20, main = "funded_amnt distribution", xlab = "funded_amnt", col = "lightblue", border = "black")

as we can see, funded_amnt is almost the same as the loan_amnt column, consequently, we remove it.

lc_data$funded_amnt <- NULL 

let’s examine the funded_amnt_inv column

sum(is.na(lc_data$funded_amnt_inv))
[1] 0
cor(lc_data$funded_amnt_inv, lc_data$int_rate)
[1] 0.1449083
hist(lc_data$funded_amnt_inv, breaks = 20, main = "funded_amnt_inv distribution", xlab = "funded_amnt_inv", col = "lightblue", border = "black")

remove funded_amnt_inv for the same reason as above

lc_data$funded_amnt_inv <- NULL

let’s see the int_rate distribution.

hist(lc_data$int_rate, breaks = 20, main = "int_rate distribution", xlab = "int_rate", col = "lightblue", border = "black")

Standardize int rate:

#lc_data$int_rate <- scale(lc_data$int_rate)

we delete the emp_title column as there are several entries for the same job title and because there are too many different values for one-hot encoding. In addition, some titles are unclear (NLP required)

n_distinct(lc_data$emp_title)
[1] 0

As we can observe, there are 40363 NAs. We can assume 40363 do not work.

barplot(table(lc_data$emp_length),
        xlab = "emp_length years", 
        ylab = "Frequency", 
        col = "skyblue", 
        border = "black",
        cex.names = 0.6)  # The size of the main title

Since emp_length seems to be categorical, we transform it to as a factor and then as numeric. The conversion to numeric is needed for supporting the XGBoost

lc_data$emp_length <- as.factor(lc_data$emp_length)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=emp_length)) + geom_boxplot()

lc_data$emp_length <- as.numeric(lc_data$emp_length)

term

lc_data$term <- as.factor(lc_data$term)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=term)) + geom_boxplot()

lc_data$term <- as.numeric(lc_data$term)
boh = order(lc_data$term)

Cleaning of home_ownership:

During the data cleaning phase, our analysis revealed that the variable “home_ownership” does not show a distinct correlation with interest rates. Specifically, among the categories, “ANY” and “OTHER” contain 2 and 154 cases, respectively, while the “NONE” category comprises 39 cases. Although the “NONE” category appears to demonstrate a higher interest rate compared to others, the limited sample size of 39 cases raises doubts about the reliability of this observation. Notably, the “NONE” category might pertain to individuals experiencing homelessness, prompting ethical concerns about loan provision to this demographic.

table(lc_data$home_ownership)

     ANY MORTGAGE     NONE    OTHER      OWN     RENT 
       2   399151       45      155    78789   320499 
ggplot(data = lc_data, mapping = aes(x=int_rate,y=home_ownership)) + geom_boxplot()

Then, we retain mortgage, own and rent:

lc_data <- lc_data %>% filter(home_ownership %in% c("MORTGAGE","OWN","RENT"))
lc_data$home_ownership <- as.numeric(as.factor(lc_data$home_ownership))

application joint handling


# merging annual income
lc_data <- lc_data %>% mutate(
    annual_inc_merged = ifelse(is.na(annual_inc_joint)== TRUE, annual_inc,annual_inc_joint)) 

lc_data <- lc_data %>% select(-annual_inc,-annual_inc_joint)


# merging debt to income ratio
lc_data <- lc_data %>% mutate(
    dti_merged = ifelse(is.na(dti_joint)== TRUE, dti,dti_joint)) 

lc_data <- lc_data %>% select(-dti,-dti_joint)

Upon reviewing the summary again, it becomes apparent that there are merely 460 joint applications, constituting a small subset within the extensive dataset of around 800k rows. Through consolidating the debt-to-income ratios (dti’s), we can pinpoint the data pertinent to our research objectives. Hence, it is advisable to eliminate the columns verification_status_joint and application_type to prevent introducing unwarranted variability into our analysis.

table(lc_data$verification_status)

   Not Verified Source Verified        Verified 
         240255          296631          261553 
table(lc_data$verification_status_joint)

                   Not Verified Source Verified        Verified 
         797979             253              53             154 
lc_data$verification_status <- as.numeric(as.factor(lc_data$verification_status))
lc_data <- lc_data %>% select(-verification_status_joint, -application_type)

Let’s checl if other is NA or a real value for purpose. It’s a real one, so we don’t have to handle it.

lc_data$purpose <- as.factor(lc_data$purpose)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=purpose)) + geom_boxplot()

lc_data$purpose <- as.numeric(lc_data$purpose)

Let’s have a glance to the state address:

table(lc_data$addr_state)

    AK     AL     AR     AZ     CA     CO     CT     DC     DE     FL     GA 
  1992  10101   5953  18359 116578  16934  12154   2188   2268  54819  26146 
    HI     IA     ID     IL     IN     KS     KY     LA     MA     MD     ME 
  4112     13     11  31880  12393   7105   7726   9498  18546  18906    469 
    MI     MN     MO     MS     MT     NC     ND     NE     NH     NJ     NM 
 20678  14306  12821   3455   2286  22135    431   1064   3865  29991   4428 
    NV     NY     OH     OK     OR     PA     RI     SC     SD     TN     TX 
 11155  66790  26682   7266   9806  28221   3499   9609   1615  11618  63982 
    UT     VA     VT     WA     WI     WV     WY 
  5629  23616   1606  17470  10446   3977   1841 
lc_data$addr_state <- as.factor(lc_data$addr_state)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=addr_state)) + geom_boxplot()

lc_data$addr_state <- as.numeric(lc_data$addr_state)

Regarding delinquency in the last 2 years, there are few NAs then remove them:

lc_data <- lc_data %>% 
    filter(!(is.na(delinq_2yrs)))
lc_data <- lc_data %>%
  mutate(mths_since_delinq_cat = ifelse(
    is.na(mths_since_last_delinq) == TRUE,
    "NONE",
    ifelse(
      mths_since_last_delinq <= 12,
      "Less_1_Y",
      ifelse(
        mths_since_last_delinq <= 24,
        "Less_2_Y",
        ifelse(
          mths_since_last_delinq <= 36,
          "Less_3_Y",
          ifelse(mths_since_last_delinq <= 48, "Less_4_Y", "More_4_Y")
        )
      )
    )
  )) %>% select(-mths_since_last_delinq)
          
lc_data$mths_since_delinq_cat <- as.factor(lc_data$mths_since_delinq_cat)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=mths_since_delinq_cat))+geom_boxplot()

lc_data$mths_since_delinq_cat <- as.numeric(lc_data$mths_since_delinq_cat)
lc_data <- lc_data %>%
  mutate(mths_since_last_record_cat = ifelse(
    is.na(mths_since_last_record) == TRUE,
    "NONE",
    ifelse(
      mths_since_last_record <= 12,
      "Less_1_Y",
      ifelse(
        mths_since_last_record <= 24,
        "Less_2_Y",
        ifelse(
          mths_since_last_record <= 36,
          "Less_3_Y",
          ifelse(mths_since_last_record <= 48, "Less_4_Y", "More_4_Y")
        )
      )
    )
  )) %>% select(-mths_since_last_record)

lc_data$mths_since_last_record_cat <- as.factor(lc_data$mths_since_last_record_cat)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=mths_since_last_record_cat))+geom_boxplot()

lc_data$mths_since_last_record_cat <- as.numeric(lc_data$mths_since_last_record_cat)
lc_data <-lc_data %>% 
  mutate(mths_since_last_major_derog_cat =  ifelse(
    is.na(mths_since_last_major_derog) == TRUE,
    "NONE",
    ifelse(
      mths_since_last_major_derog <= 12,
      "Less_1_Y",
      ifelse(
        mths_since_last_major_derog <= 24,
        "Less_2_Y",
        ifelse(
          mths_since_last_major_derog <= 36,
          "Less_3_Y",
          ifelse(mths_since_last_major_derog <= 48, "Less_4_Y", "More_4_Y")
        )
      )
    )
  )) %>% select(-mths_since_last_major_derog)

lc_data$mths_since_last_major_derog_cat <- as.factor(lc_data$mths_since_last_major_derog_cat)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=mths_since_last_major_derog_cat))+geom_boxplot()

lc_data$mths_since_last_major_derog_cat <- as.numeric(lc_data$mths_since_last_major_derog_cat)
lc_data$initial_list_status <- as.factor(lc_data$initial_list_status)
ggplot(data = lc_data, mapping = aes(x=int_rate,y=initial_list_status))+geom_boxplot()

lc_data$initial_list_status <- as.numeric(lc_data$initial_list_status)

Let’s check which columns still have null values

colSums(is.na(lc_data))
                      loan_amnt                            term 
                              0                               0 
                       int_rate                      emp_length 
                              0                               0 
                 home_ownership             verification_status 
                              0                               0 
                        purpose                      addr_state 
                              0                               0 
                    delinq_2yrs                earliest_cr_line 
                              0                               0 
                 inq_last_6mths                        open_acc 
                              0                               0 
                        pub_rec                       revol_bal 
                              0                               2 
                     revol_util                       total_acc 
                            428                               0 
            initial_list_status      collections_12_mths_ex_med 
                              0                              99 
                 acc_now_delinq                    tot_coll_amt 
                              0                           63132 
                    tot_cur_bal                     open_acc_6m 
                          63132                          779302 
                     open_il_6m                     open_il_12m 
                         779302                          779302 
                    open_il_24m              mths_since_rcnt_il 
                         779302                          779807 
                   total_bal_il                         il_util 
                         779302                          781784 
                    open_rv_12m                     open_rv_24m 
                         779302                          779302 
                     max_bal_bc                        all_util 
                         779302                          779302 
               total_rev_hi_lim                          inq_fi 
                          63132                          779302 
                    total_cu_tl                    inq_last_12m 
                         779302                          779302 
              annual_inc_merged                      dti_merged 
                              0                               0 
          mths_since_delinq_cat      mths_since_last_record_cat 
                              0                               0 
mths_since_last_major_derog_cat 
                              0 

The columns revol_bal and revol_util contain only few NA values, those values can’t be replaced with 0, then we filter the values which are not NA

lc_data <- lc_data %>% 
    filter(!(is.na(revol_bal))) %>% 
        filter(!(is.na(revol_util)))

Let’s check which columns still have null values

names(which(colSums(is.na(lc_data)) > 0))
 [1] "collections_12_mths_ex_med" "tot_coll_amt"              
 [3] "tot_cur_bal"                "open_acc_6m"               
 [5] "open_il_6m"                 "open_il_12m"               
 [7] "open_il_24m"                "mths_since_rcnt_il"        
 [9] "total_bal_il"               "il_util"                   
[11] "open_rv_12m"                "open_rv_24m"               
[13] "max_bal_bc"                 "all_util"                  
[15] "total_rev_hi_lim"           "inq_fi"                    
[17] "total_cu_tl"                "inq_last_12m"              

Replace null values with 0 where is possible

lc_data <-
  lc_data %>%
  mutate(open_acc_6m = ifelse(is.na(open_acc_6m) == TRUE, 0, open_acc_6m)) %>%
  mutate(tot_cur_bal = ifelse(is.na(tot_cur_bal) == TRUE, 0, tot_cur_bal)) %>%
  mutate(open_il_6m = ifelse(is.na(open_il_6m) == TRUE, 0, open_il_6m)) %>%
  mutate(open_il_12m = ifelse(is.na(open_il_12m) == TRUE, 0, open_il_12m)) %>%
  mutate(open_il_24m = ifelse(is.na(open_il_24m) == TRUE, 0, open_il_24m)) %>%
  mutate(mths_since_rcnt_il = ifelse(is.na(mths_since_rcnt_il) == TRUE, 0, mths_since_rcnt_il)) %>%
  mutate(total_bal_il = ifelse(is.na(total_bal_il) == TRUE, 0, total_bal_il)) %>%
  mutate(il_util = ifelse(is.na(il_util) == TRUE, 0, il_util)) %>%
  mutate(open_rv_12m = ifelse(is.na(open_rv_12m) == TRUE, 0, open_rv_12m)) %>%
  mutate(total_rev_hi_lim = ifelse(is.na(total_rev_hi_lim) == TRUE, 0, total_rev_hi_lim)) %>%
  mutate(max_bal_bc = ifelse(is.na(max_bal_bc) == TRUE, 0, max_bal_bc)) %>%
  mutate(all_util = ifelse(is.na(all_util) == TRUE, 0, all_util)) %>%
  mutate(inq_fi = ifelse(is.na(inq_fi) == TRUE, 0, inq_fi)) %>%
  mutate(total_cu_tl = ifelse(is.na(total_cu_tl) == TRUE, 0, total_cu_tl)) %>%
  mutate(inq_last_12m = ifelse(is.na(inq_last_12m) == TRUE, 0, inq_last_12m)) %>%
  mutate(open_rv_24m = ifelse(is.na(open_rv_24m) == TRUE, 0, open_rv_24m)) %>%
  mutate(tot_coll_amt = ifelse(is.na(tot_coll_amt)== TRUE,0, tot_coll_amt)) %>%
  mutate(collections_12_mths_ex_med = ifelse(is.na(collections_12_mths_ex_med)== TRUE,0, collections_12_mths_ex_med))

earliest_cr_line contains the month the borrower’s earliest reported credit line was opened. Even if this date consists only on month and year, still there are too many unique values. We could transform the dates in to a numerical value, by converting them from date into Unix Time. This unit measures time by the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970. Since this column doesn’t contain the day number, we take as a reference the first day of the month.

lc_data <- lc_data %>% 
    filter(!(is.na(earliest_cr_line)))

# function to replace dates with unix time
to_unix_time <- function(date) {
  tmp <- paste("01", date, sep="-")
  return (as.numeric(as.POSIXct(tmp, format="%d-%b-%Y", tz="UTC")))
}

# map dates to unix time
lc_data$earliest_cr_line <- apply(lc_data, 1, function(row) to_unix_time(row["earliest_cr_line"]))

# standardize them
#lc_data$earliest_cr_line <- scale(lc_data$earliest_cr_line)

Outliers Removal.

boxplot(lc_data$int_rate)

# Identify outliers using boxplot
outliers <- boxplot(lc_data$int_rate, plot = FALSE)$out
# Remove outliers from the dataset
lc_data_clean <- lc_data[!lc_data$int_rate %in% outliers, ]
summary(lc_data)
   loan_amnt          term        int_rate       emp_length   
 Min.   :  500   Min.   :1.0   Min.   : 5.32   Min.   : 1.00  
 1st Qu.: 8000   1st Qu.:1.0   1st Qu.: 9.99   1st Qu.: 3.00  
 Median :13000   Median :1.0   Median :12.99   Median : 4.00  
 Mean   :14757   Mean   :1.3   Mean   :13.24   Mean   : 5.11  
 3rd Qu.:20000   3rd Qu.:2.0   3rd Qu.:16.20   3rd Qu.: 7.00  
 Max.   :35000   Max.   :2.0   Max.   :28.99   Max.   :12.00  
 home_ownership  verification_status    purpose         addr_state   
 Min.   :1.000   Min.   :1.000       Min.   : 1.000   Min.   : 1.00  
 1st Qu.:1.000   1st Qu.:1.000       1st Qu.: 3.000   1st Qu.:10.00  
 Median :2.000   Median :2.000       Median : 3.000   Median :24.00  
 Mean   :1.901   Mean   :2.027       Mean   : 3.571   Mean   :24.14  
 3rd Qu.:3.000   3rd Qu.:3.000       3rd Qu.: 3.000   3rd Qu.:37.00  
 Max.   :3.000   Max.   :3.000       Max.   :14.000   Max.   :51.00  
  delinq_2yrs      earliest_cr_line     inq_last_6mths       open_acc    
 Min.   : 0.0000   Min.   :-820540800   Min.   : 0.0000   Min.   : 1.00  
 1st Qu.: 0.0000   1st Qu.: 770428800   1st Qu.: 0.0000   1st Qu.: 8.00  
 Median : 0.0000   Median : 936144000   Median : 0.0000   Median :11.00  
 Mean   : 0.3143   Mean   : 889273164   Mean   : 0.6947   Mean   :11.55  
 3rd Qu.: 0.0000   3rd Qu.:1051747200   3rd Qu.: 1.0000   3rd Qu.:14.00  
 Max.   :39.0000   Max.   :1351728000   Max.   :33.0000   Max.   :90.00  
    pub_rec          revol_bal         revol_util       total_acc     
 Min.   : 0.0000   Min.   :      0   Min.   :  0.00   Min.   :  1.00  
 1st Qu.: 0.0000   1st Qu.:   6450   1st Qu.: 37.70   1st Qu.: 17.00  
 Median : 0.0000   Median :  11881   Median : 56.00   Median : 24.00  
 Mean   : 0.1954   Mean   :  16934   Mean   : 55.05   Mean   : 25.27  
 3rd Qu.: 0.0000   3rd Qu.:  20844   3rd Qu.: 73.50   3rd Qu.: 32.00  
 Max.   :63.0000   Max.   :2904836   Max.   :892.30   Max.   :169.00  
 initial_list_status collections_12_mths_ex_med acc_now_delinq     
 Min.   :1.000       Min.   : 0.00000           Min.   : 0.000000  
 1st Qu.:1.000       1st Qu.: 0.00000           1st Qu.: 0.000000  
 Median :1.000       Median : 0.00000           Median : 0.000000  
 Mean   :1.485       Mean   : 0.01448           Mean   : 0.005026  
 3rd Qu.:2.000       3rd Qu.: 0.00000           3rd Qu.: 0.000000  
 Max.   :2.000       Max.   :20.00000           Max.   :14.000000  
  tot_coll_amt      tot_cur_bal       open_acc_6m         open_il_6m      
 Min.   :      0   Min.   :      0   Min.   : 0.00000   Min.   : 0.00000  
 1st Qu.:      0   1st Qu.:  23195   1st Qu.: 0.00000   1st Qu.: 0.00000  
 Median :      0   Median :  65402   Median : 0.00000   Median : 0.00000  
 Mean   :    210   Mean   : 128461   Mean   : 0.02641   Mean   : 0.06982  
 3rd Qu.:      0   3rd Qu.: 195864   3rd Qu.: 0.00000   3rd Qu.: 0.00000  
 Max.   :9152545   Max.   :8000078   Max.   :14.00000   Max.   :33.00000  
  open_il_12m        open_il_24m       mths_since_rcnt_il  total_bal_il   
 Min.   : 0.00000   Min.   : 0.00000   Min.   :  0.0000   Min.   :     0  
 1st Qu.: 0.00000   1st Qu.: 0.00000   1st Qu.:  0.0000   1st Qu.:     0  
 Median : 0.00000   Median : 0.00000   Median :  0.0000   Median :     0  
 Mean   : 0.01816   Mean   : 0.03991   Mean   :  0.4918   Mean   :   872  
 3rd Qu.: 0.00000   3rd Qu.: 0.00000   3rd Qu.:  0.0000   3rd Qu.:     0  
 Max.   :12.00000   Max.   :19.00000   Max.   :363.0000   Max.   :878459  
    il_util         open_rv_12m        open_rv_24m         max_bal_bc     
 Min.   :  0.000   Min.   : 0.00000   Min.   : 0.00000   Min.   :    0.0  
 1st Qu.:  0.000   1st Qu.: 0.00000   1st Qu.: 0.00000   1st Qu.:    0.0  
 Median :  0.000   Median : 0.00000   Median : 0.00000   Median :    0.0  
 Mean   :  1.489   Mean   : 0.03316   Mean   : 0.07114   Mean   :  140.8  
 3rd Qu.:  0.000   3rd Qu.: 0.00000   3rd Qu.: 0.00000   3rd Qu.:    0.0  
 Max.   :223.300   Max.   :22.00000   Max.   :43.00000   Max.   :83047.0  
    all_util       total_rev_hi_lim      inq_fi          total_cu_tl      
 Min.   :  0.000   Min.   :      0   Min.   : 0.00000   Min.   : 0.00000  
 1st Qu.:  0.000   1st Qu.:  11700   1st Qu.: 0.00000   1st Qu.: 0.00000  
 Median :  0.000   Median :  21800   Median : 0.00000   Median : 0.00000  
 Mean   :  1.456   Mean   :  29564   Mean   : 0.02262   Mean   : 0.03668  
 3rd Qu.:  0.000   3rd Qu.:  37900   3rd Qu.: 0.00000   3rd Qu.: 0.00000  
 Max.   :151.400   Max.   :9999999   Max.   :16.00000   Max.   :35.00000  
  inq_last_12m      annual_inc_merged   dti_merged    mths_since_delinq_cat
 Min.   :-4.00000   Min.   :   1896   Min.   : 0.00   Min.   :1.000        
 1st Qu.: 0.00000   1st Qu.:  45000   1st Qu.:11.91   1st Qu.:3.000        
 Median : 0.00000   Median :  65000   Median :17.66   Median :6.000        
 Mean   : 0.04733   Mean   :  75038   Mean   :18.13   Mean   :4.576        
 3rd Qu.: 0.00000   3rd Qu.:  90000   3rd Qu.:23.94   3rd Qu.:6.000        
 Max.   :32.00000   Max.   :9500000   Max.   :43.86   Max.   :6.000        
 mths_since_last_record_cat mths_since_last_major_derog_cat
 Min.   :1.000              Min.   :1.000                  
 1st Qu.:6.000              1st Qu.:6.000                  
 Median :6.000              Median :6.000                  
 Mean   :5.779              Mean   :5.435                  
 3rd Qu.:6.000              3rd Qu.:6.000                  
 Max.   :6.000              Max.   :6.000                  

# TODO: (parte vecchia), split 80/20 e linear regression...
# Create indices for splitting (80% train, 20% test)
train_indices <- createDataPartition(lc_data$int_rate, p = 0.8, list = FALSE)

# Create training and testing datasets
train_data <- lc_data[train_indices, ]
test_data <- lc_data[-train_indices, ]

#### Linear Regression ####
#lm.fit <- lm(int_rate ~ ., data = train_data)

# TODO: check collinearity and multicollinearity
#vif(lm.fit) # there is multicollinearity
#cor(lc_data) 

# Make predictions on training and testing data
#train_predictions <- predict(lm.fit, newdata = train_data)
#test_predictions <- predict(lm.fit, newdata = test_data)

# Evaluate model performance on training data
#train_rmse <- sqrt(mean((train_predictions - train_data$int_rate)^2))
#train_r_squared <- summary(lm.fit)$r.squared

# Evaluate model performance on testing data
#test_rmse <- sqrt(mean((test_predictions - test_data$int_rate)^2))
#test_r_squared <- summary(lm.fit, test_data)$r.squared

# Print evaluation metrics
#cat("Training RMSE:", train_rmse, "\n")
#cat("Training R-squared:", train_r_squared, "\n")
#rmse <- sqrt(mean(lm.fit$residuals^2))
#print(rmse)
#### Linear Regression ####

lm.fit <- lm(int_rate ~ ., data = train_data)

# Make predictions on the training and testing data
lm.train_predictions <- predict(lm.fit, newdata = train_data)
lm.test_predictions <- predict(lm.fit, newdata = test_data)

# Calculate Mean Squared Error (MSE) for training and testing
lm.train_mse <- mean((lm.train_predictions - train_data$int_rate)^2)
lm.test_mse <- mean((lm.test_predictions - test_data$int_rate)^2)

# Calculate Root Mean Squared Error (RMSE) for training and testing
lm.train_rmse <- sqrt(lm.train_mse)
lm.test_rmse <- sqrt(lm.test_mse)

# Calculate Mean Absolute Error (MAE) for training and testing
lm.train_mae <- mean(abs(lm.train_predictions - train_data$int_rate))
lm.test_mae <- mean(abs(lm.test_predictions - test_data$int_rate))

# Calculate R-squared (R²) for training and testing
lm.train_r2 <- 1 - (sum((train_data$int_rate - lm.train_predictions)^2) / sum((train_data$int_rate - mean(train_data$int_rate))^2))
lm.test_r2 <- 1 - (sum((test_data$int_rate - lm.test_predictions)^2) / sum((test_data$int_rate - mean(test_data$int_rate))^2))

# Display the metrics
cat("Training MSE:", lm.train_mse, "\n")
Training MSE: 10.69433 
cat("Testing MSE:", lm.test_mse, "\n")
Testing MSE: 11.08025 
cat("Training RMSE:", lm.train_rmse, "\n")
Training RMSE: 3.270218 
cat("Testing RMSE:", lm.test_rmse, "\n")
Testing RMSE: 3.328701 
cat("Training MAE:", lm.train_mae, "\n")
Training MAE: 2.591549 
cat("Testing MAE:", lm.test_mae, "\n")
Testing MAE: 2.592027 
cat("Training R-squared (R²):", lm.train_r2, "\n")
Training R-squared (R²): 0.4431432 
cat("Testing R-squared (R²):", lm.test_r2, "\n")
Testing R-squared (R²): 0.4227827 

Lasso It standardizes data automatically

lasso.predictors_train <- model.matrix(int_rate ~ ., train_data)[,-1]
lasso.target_train <- train_data$int_rate
lasso.predictors_test <- model.matrix(int_rate ~ ., test_data)[,-1]
lasso.target_test <- test_data$int_rate

lasso.fit <- glmnet(lasso.predictors_train, lasso.target_train, alpha = 1)

dim(coef(lasso.fit))
[1] 41 70
# We have only 69 rows, because glmnet has a stop criterion, see help.

lasso.fit

Call:  glmnet(x = lasso.predictors_train, y = lasso.target_train, alpha = 1) 

   Df  %Dev  Lambda
1   0  0.00 1.87700
2   1  3.11 1.71000
3   1  5.70 1.55800
4   1  7.85 1.42000
5   1  9.63 1.29400
6   1 11.11 1.17900
7   2 12.73 1.07400
8   3 15.01 0.97850
9   4 17.72 0.89160
10  4 20.19 0.81240
11  4 22.24 0.74020
12  6 24.54 0.67450
13  6 26.76 0.61450
14  7 28.69 0.55990
15  7 30.41 0.51020
16  9 32.15 0.46490
17 10 33.70 0.42360
18 10 35.04 0.38600
19 12 36.20 0.35170
20 13 37.34 0.32040
21 13 38.30 0.29200
22 14 39.11 0.26600
23 15 39.85 0.24240
24 15 40.47 0.22090
25 15 40.99 0.20120
26 16 41.42 0.18340
27 17 41.81 0.16710
28 17 42.13 0.15220
29 17 42.40 0.13870
30 17 42.62 0.12640
31 17 42.80 0.11520
32 19 42.97 0.10490
33 20 43.14 0.09560
34 20 43.28 0.08711
35 20 43.40 0.07937
36 21 43.52 0.07232
37 22 43.63 0.06590
38 23 43.72 0.06004
39 25 43.80 0.05471
40 27 43.88 0.04985
41 28 43.94 0.04542
42 31 44.00 0.04138
43 31 44.05 0.03771
44 31 44.09 0.03436
45 31 44.13 0.03131
46 31 44.16 0.02852
47 32 44.18 0.02599
48 33 44.21 0.02368
49 33 44.22 0.02158
50 33 44.24 0.01966
51 33 44.25 0.01791
52 33 44.26 0.01632
53 33 44.27 0.01487
54 35 44.28 0.01355
55 35 44.28 0.01235
56 35 44.29 0.01125
57 35 44.29 0.01025
58 36 44.30 0.00934
59 36 44.30 0.00851
60 36 44.30 0.00775
61 36 44.30 0.00707
62 36 44.30 0.00644
63 37 44.31 0.00587
64 37 44.31 0.00534
65 37 44.31 0.00487
66 37 44.31 0.00444
67 37 44.31 0.00404
68 37 44.31 0.00368
69 37 44.31 0.00336
70 37 44.31 0.00306
# We observe that some of the coefficients are set to zero (Df=0, %Dev=0)

plot(lasso.fit, label=TRUE)

# Also here we can see that some of the coefficients are set to zero.
# Make predictions on the training and testing data

lasso.train_predictions <- predict(lasso.fit, newdata = train_data, newx = lasso.predictors_train)
lasso.test_predictions <- predict(lasso.fit, newdata = test_data, newx = lasso.predictors_train)

# Calculate Mean Squared Error (MSE) for training and testing
lasso.train_mse <- mean((lasso.train_predictions - train_data$int_rate)^2)
lasso.test_mse <- mean((lasso.test_predictions - test_data$int_rate)^2)
Warning: longer object length is not a multiple of shorter object length
# Calculate Root Mean Squared Error (RMSE) for training and testing
lasso.train_rmse <- sqrt(lasso.train_mse)
lasso.test_rmse <- sqrt(lasso.test_mse)

# Calculate Mean Absolute Error (MAE) for training and testing
lasso.train_mae <- mean(abs(lasso.train_predictions - train_data$int_rate))
lasso.test_mae <- mean(abs(lasso.test_predictions - test_data$int_rate))
Warning: longer object length is not a multiple of shorter object length
# Calculate R-squared (R²) for training and testing
lasso.train_r2 <- 1 - (sum((train_data$int_rate - lasso.train_predictions)^2) / sum((train_data$int_rate - mean(train_data$int_rate))^2))
lasso.test_r2 <- 1 - (sum((test_data$int_rate - lasso.test_predictions)^2) / sum((test_data$int_rate - mean(test_data$int_rate))^2))
Warning: longer object length is not a multiple of shorter object length
# Display the metrics
cat("Training MSE:", lasso.train_mse, "\n")
Training MSE: 12.12513 
cat("Testing MSE:", lasso.test_mse, "\n")
Testing MSE: 24.93444 
cat("Training RMSE:", lasso.train_rmse, "\n")
Training RMSE: 3.482116 
cat("Testing RMSE:", lasso.test_rmse, "\n")
Testing RMSE: 4.993439 
cat("Training MAE:", lasso.train_mae, "\n")
Training MAE: 2.767999 
cat("Testing MAE:", lasso.test_mae, "\n")
Testing MAE: 3.995769 
cat("Training R-squared (R²):", lasso.train_r2, "\n")
Training R-squared (R²): -43.19514 
cat("Testing R-squared (R²):", lasso.test_r2, "\n")
Testing R-squared (R²): -362.708 

K fold using K=5:

# Define the number of folds for cross-validation
num_folds <- 5
folds <- createFolds(train_data$int_rate, k = num_folds, list = TRUE)

K fold using K=5 and linear regression:

#### Linear Regresion applying Cross Validation with k=5  ####

# Initialize lists to store models and their results
lm.k5.models <- list()
lm.k5.results <- data.frame()

# Perform k-fold cross-validation
for(i in seq_along(folds)) {
  # Split the data into training and testing for the current fold
  train_indices <- folds[[i]]
  test_indices <- setdiff(seq_len(nrow(train_data)), train_indices)
  
  train_data_fold <- train_data[train_indices, ]
  test_data_fold <- train_data[test_indices, ]
  
  # Fit the model on the training fold
  lm.k5 <- lm(int_rate ~ ., data = train_data_fold)
  lm.k5.models[[i]] <- lm.k5  # Store the model
  
  # Make predictions on the training and testing fold
  lm.k5.train_predictions <- predict(lm.k5, newdata = train_data_fold)
  lm.k5.test_predictions <- predict(lm.k5, newdata = test_data_fold)
  
  # Calculate metrics for training fold
  lm.k5.train_mse <- mean((lm.k5.train_predictions - train_data_fold$int_rate)^2)
  lm.k5.train_rmse <- sqrt(lm.k5.train_mse)
  lm.k5.train_mae <- mean(abs(lm.k5.train_predictions - train_data_fold$int_rate))
  lm.k5.train_r2 <- summary(lm.k5)$r.squared
  
  # Calculate metrics for testing fold
  lm.k5.test_mse <- mean((lm.k5.test_predictions - test_data_fold$int_rate)^2)
  lm.k5.test_rmse <- sqrt(lm.k5.test_mse)
  lm.k5.test_mae <- mean(abs(lm.k5.test_predictions - test_data_fold$int_rate))
  lm.k5.test_r2 <- 1 - (sum((test_data_fold$int_rate - lm.k5.test_predictions)^2) / sum((test_data_fold$int_rate - mean(test_data_fold$int_rate))^2))
  
  # Store metrics in the results dataframe
  lm.k5.results <- rbind(lm.k5.results, data.frame(
    Fold = i,
    Train_MSE = lm.k5.train_mse, Test_MSE = lm.k5.test_mse,
    Train_RMSE = lm.k5.train_rmse, Test_RMSE = lm.k5.test_rmse,
    Train_MAE = lm.k5.train_mae, Test_MAE = lm.k5.test_mae,
    Train_R2 = lm.k5.train_r2, Test_R2 = lm.k5.test_r2
  ))
}

# Display the models and their metrics
print(lm.k5.models)
[[1]]

Call:
lm(formula = int_rate ~ ., data = train_data_fold)

Coefficients:
                    (Intercept)                        loan_amnt  
                      3.990e+00                        3.224e-05  
                           term                       emp_length  
                      3.921e+00                        1.270e-02  
                 home_ownership              verification_status  
                      2.519e-01                        7.589e-01  
                        purpose                       addr_state  
                      3.385e-01                        4.820e-05  
                    delinq_2yrs                 earliest_cr_line  
                      3.545e-02                        1.869e-09  
                 inq_last_6mths                         open_acc  
                      9.654e-01                        6.661e-02  
                        pub_rec                        revol_bal  
                      3.458e-01                        3.940e-06  
                     revol_util                        total_acc  
                      4.253e-02                       -3.598e-02  
            initial_list_status       collections_12_mths_ex_med  
                     -1.015e+00                        2.179e-01  
                 acc_now_delinq                     tot_coll_amt  
                      1.025e+00                        2.255e-05  
                    tot_cur_bal                      open_acc_6m  
                     -1.205e-06                        3.512e-04  
                     open_il_6m                      open_il_12m  
                     -1.427e-01                        6.414e-01  
                    open_il_24m               mths_since_rcnt_il  
                     -5.466e-02                       -7.403e-03  
                   total_bal_il                          il_util  
                      4.214e-07                       -1.237e-03  
                    open_rv_12m                      open_rv_24m  
                      1.658e-01                        6.001e-02  
                     max_bal_bc                         all_util  
                     -4.794e-05                       -5.778e-03  
               total_rev_hi_lim                           inq_fi  
                     -1.649e-05                        5.575e-02  
                    total_cu_tl                     inq_last_12m  
                     -4.534e-02                        5.826e-02  
              annual_inc_merged                       dti_merged  
                     -3.387e-06                        5.134e-02  
          mths_since_delinq_cat       mths_since_last_record_cat  
                     -2.020e-01                       -1.925e-01  
mths_since_last_major_derog_cat  
                     -1.434e-01  


[[2]]

Call:
lm(formula = int_rate ~ ., data = train_data_fold)

Coefficients:
                    (Intercept)                        loan_amnt  
                      3.686e+00                        2.412e-05  
                           term                       emp_length  
                      3.944e+00                        1.660e-02  
                 home_ownership              verification_status  
                      2.446e-01                        7.453e-01  
                        purpose                       addr_state  
                      3.437e-01                       -4.542e-04  
                    delinq_2yrs                 earliest_cr_line  
                      3.112e-02                        1.998e-09  
                 inq_last_6mths                         open_acc  
                      9.976e-01                        4.858e-02  
                        pub_rec                        revol_bal  
                      3.926e-01                       -6.629e-06  
                     revol_util                        total_acc  
                      4.792e-02                       -3.290e-02  
            initial_list_status       collections_12_mths_ex_med  
                     -1.072e+00                        3.118e-01  
                 acc_now_delinq                     tot_coll_amt  
                      1.259e+00                        2.134e-05  
                    tot_cur_bal                      open_acc_6m  
                     -1.514e-06                        1.265e-01  
                     open_il_6m                      open_il_12m  
                     -1.423e-01                        6.040e-01  
                    open_il_24m               mths_since_rcnt_il  
                     -4.183e-02                       -6.549e-03  
                   total_bal_il                          il_util  
                      2.070e-06                       -5.289e-03  
                    open_rv_12m                      open_rv_24m  
                      1.256e-01                        5.293e-02  
                     max_bal_bc                         all_util  
                     -8.822e-05                        1.001e-03  
               total_rev_hi_lim                           inq_fi  
                     -3.157e-06                        9.824e-02  
                    total_cu_tl                     inq_last_12m  
                     -8.761e-02                        5.175e-02  
              annual_inc_merged                       dti_merged  
                     -3.618e-06                        5.119e-02  
          mths_since_delinq_cat       mths_since_last_record_cat  
                     -2.071e-01                       -1.762e-01  
mths_since_last_major_derog_cat  
                     -1.537e-01  


[[3]]

Call:
lm(formula = int_rate ~ ., data = train_data_fold)

Coefficients:
                    (Intercept)                        loan_amnt  
                      3.844e+00                        3.608e-05  
                           term                       emp_length  
                      3.867e+00                        1.478e-02  
                 home_ownership              verification_status  
                      2.625e-01                        7.419e-01  
                        purpose                       addr_state  
                      3.363e-01                        6.072e-04  
                    delinq_2yrs                 earliest_cr_line  
                      4.205e-02                        1.888e-09  
                 inq_last_6mths                         open_acc  
                      9.894e-01                        6.404e-02  
                        pub_rec                        revol_bal  
                      4.010e-01                        5.549e-06  
                     revol_util                        total_acc  
                      4.195e-02                       -3.446e-02  
            initial_list_status       collections_12_mths_ex_med  
                     -1.025e+00                        3.267e-01  
                 acc_now_delinq                     tot_coll_amt  
                      1.465e+00                        3.632e-05  
                    tot_cur_bal                      open_acc_6m  
                     -1.007e-06                       -3.460e-03  
                     open_il_6m                      open_il_12m  
                     -1.426e-01                        6.240e-01  
                    open_il_24m               mths_since_rcnt_il  
                     -6.981e-02                       -9.031e-03  
                   total_bal_il                          il_util  
                      1.895e-06                       -5.360e-03  
                    open_rv_12m                      open_rv_24m  
                      2.909e-01                       -2.050e-02  
                     max_bal_bc                         all_util  
                     -5.555e-05                       -3.321e-04  
               total_rev_hi_lim                           inq_fi  
                     -1.705e-05                       -2.927e-03  
                    total_cu_tl                     inq_last_12m  
                     -3.094e-02                        7.681e-02  
              annual_inc_merged                       dti_merged  
                     -4.850e-06                        5.052e-02  
          mths_since_delinq_cat       mths_since_last_record_cat  
                     -1.951e-01                       -1.516e-01  
mths_since_last_major_derog_cat  
                     -1.509e-01  


[[4]]

Call:
lm(formula = int_rate ~ ., data = train_data_fold)

Coefficients:
                    (Intercept)                        loan_amnt  
                      3.731e+00                        2.577e-05  
                           term                       emp_length  
                      3.911e+00                        1.302e-02  
                 home_ownership              verification_status  
                      2.586e-01                        7.454e-01  
                        purpose                       addr_state  
                      3.438e-01                        4.575e-05  
                    delinq_2yrs                 earliest_cr_line  
                      4.651e-02                        1.988e-09  
                 inq_last_6mths                         open_acc  
                      1.000e+00                        4.523e-02  
                        pub_rec                        revol_bal  
                      4.347e-01                       -3.958e-06  
                     revol_util                        total_acc  
                      4.736e-02                       -3.143e-02  
            initial_list_status       collections_12_mths_ex_med  
                     -1.091e+00                        3.666e-01  
                 acc_now_delinq                     tot_coll_amt  
                      1.285e+00                        3.192e-05  
                    tot_cur_bal                      open_acc_6m  
                     -1.527e-06                       -7.028e-02  
                     open_il_6m                      open_il_12m  
                     -1.060e-01                        7.431e-01  
                    open_il_24m               mths_since_rcnt_il  
                     -8.725e-02                       -1.108e-02  
                   total_bal_il                          il_util  
                     -2.061e-07                       -5.789e-03  
                    open_rv_12m                      open_rv_24m  
                      2.228e-01                        5.915e-02  
                     max_bal_bc                         all_util  
                     -7.615e-05                        2.525e-03  
               total_rev_hi_lim                           inq_fi  
                     -3.420e-06                        6.850e-02  
                    total_cu_tl                     inq_last_12m  
                     -8.391e-02                        4.464e-02  
              annual_inc_merged                       dti_merged  
                     -4.028e-06                        5.019e-02  
          mths_since_delinq_cat       mths_since_last_record_cat  
                     -2.029e-01                       -1.855e-01  
mths_since_last_major_derog_cat  
                     -1.429e-01  


[[5]]

Call:
lm(formula = int_rate ~ ., data = train_data_fold)

Coefficients:
                    (Intercept)                        loan_amnt  
                      3.673e+00                        3.206e-05  
                           term                       emp_length  
                      3.892e+00                        1.744e-02  
                 home_ownership              verification_status  
                      2.754e-01                        7.294e-01  
                        purpose                       addr_state  
                      3.328e-01                        6.610e-04  
                    delinq_2yrs                 earliest_cr_line  
                      2.837e-02                        1.893e-09  
                 inq_last_6mths                         open_acc  
                      9.921e-01                        6.424e-02  
                        pub_rec                        revol_bal  
                      4.228e-01                        7.145e-06  
                     revol_util                        total_acc  
                      4.255e-02                       -3.639e-02  
            initial_list_status       collections_12_mths_ex_med  
                     -9.944e-01                        2.516e-01  
                 acc_now_delinq                     tot_coll_amt  
                      1.367e+00                        2.477e-05  
                    tot_cur_bal                      open_acc_6m  
                     -1.040e-06                        8.671e-02  
                     open_il_6m                      open_il_12m  
                     -1.448e-01                        5.623e-01  
                    open_il_24m               mths_since_rcnt_il  
                     -2.528e-02                       -4.976e-03  
                   total_bal_il                          il_util  
                      3.319e-06                       -3.528e-03  
                    open_rv_12m                      open_rv_24m  
                      1.595e-01                        6.265e-02  
                     max_bal_bc                         all_util  
                     -7.606e-05                       -2.039e-03  
               total_rev_hi_lim                           inq_fi  
                     -1.711e-05                       -3.026e-02  
                    total_cu_tl                     inq_last_12m  
                     -6.547e-02                        8.003e-02  
              annual_inc_merged                       dti_merged  
                     -3.475e-06                        5.239e-02  
          mths_since_delinq_cat       mths_since_last_record_cat  
                     -2.008e-01                       -1.553e-01  
mths_since_last_major_derog_cat  
                     -1.422e-01  
print(lm.k5.results)
long_data <- pivot_longer(lm.k5.results, 
                          cols = -Fold,  # Exclude Fold from reshaping
                          names_to = "Metric", 
                          values_to = "Value")

# Check the reshaped data
print(head(long_data))
# Plot for Training Metrics
ggplot(long_data_training, aes(x = factor(Fold), y = Value, color = Metric)) +
  geom_point() +
  geom_line() +  # Use this only if it makes sense to connect points across folds
  labs(title = "Training Metrics per Fold - Linear Regression",
       x = "Fold",
       y = "Metric Value") +
  theme_minimal() +
  scale_color_discrete(name = "Training Metrics")


ggplot(subset(long_data, grepl("Train", Metric)), aes(x = factor(Fold), y = Value, color = Metric)) +
  geom_point() +
  geom_line() +  # Use this only if it makes sense to connect points across folds
  labs(title = "Training Metrics per Fold - Linear Regression",
       x = "Fold",
       y = "Metric Value") +
  theme_minimal() +
  scale_color_discrete(name = "Training Metrics")

# Plot for Testing Metrics

ggplot(long_data_testing, aes(x = factor(Fold), y = Value, color = Metric)) +
  geom_point() +
  geom_line() +  # Use this only if it makes sense to connect points across folds
  labs(title = "Testing Metrics per Fold - Linear Regression",
       x = "Fold",
       y = "Metric Value") +
  theme_minimal() +
  scale_color_discrete(name = "Testing Metrics")

K fold using K=5 and Random Forest:

#### Random Forest applying Cross Validation with k5  ####

# Initialize lists to store models and their results
rf.k5.models <- list()
rf.k5.results <- data.frame()

# Perform k-fold cross-validation
for(i in seq_along(folds)) {
  # Split the data into training and testing for the current fold
  train_indices <- folds[[i]]
  test_indices <- setdiff(seq_len(nrow(train_data)), train_indices)
  
  train_data_fold <- train_data[train_indices, ]
  test_data_fold <- train_data[test_indices, ]
  
  # Fit the model on the training fold
  rf.k5 <- ranger(formula = int_rate ~ ., data = train_data, num.trees = 500, verbose=TRUE, importance = "impurity", oob.error = TRUE)
  rf.k5.models[[i]] <- rf.k5  # Store the model
  
  # Make predictions on the training and testing fold
  rf.k5.train_predictions <- predict(rf.k5, data = train_data_fold)
  rf.k5.test_predictions <- predict(rf.k5, data = test_data_fold)
  
  # Calculate metrics for training fold
  rf.k5.train_mse <- mean((rf.k5.train_predictions - train_data_fold$int_rate)^2)
  rf.k5.train_rmse <- sqrt(rf.k5.train_mse)
  rf.k5.train_mae <- mean(abs(rf.k5.train_predictions - train_data_fold$int_rate))
  rf.k5.train_r2 <- summary(rf.k5)$r.squared
  
  # Calculate metrics for testing fold
  rf.k5.test_mse <- mean((rf.k5.test_predictions - test_data_fold$int_rate)^2)
  rf.k5.test_rmse <- sqrt(rf.k5.test_mse)
  rf.k5.test_mae <- mean(abs(rf.k5.test_predictions - test_data_fold$int_rate))
  rf.k5.test_r2 <- 1 - (sum((test_data_fold$int_rate - rf.k5.test_predictions)^2) / sum((test_data_fold$int_rate - mean(test_data_fold$int_rate))^2))
  
  # Store metrics in the results dataframe
  rf.k5.results <- rbind(rf.k5.results, data.frame(
    Fold = i,
    Train_MSE = rf.k5.train_mse, Test_MSE = rf.k5.test_mse,
    Train_RMSE = rf.k5.train_rmse, Test_RMSE = rf.k5.test_rmse,
    Train_MAE = rf.k5.train_mae, Test_MAE = rf.k5.test_mae,
    Train_R2 = rf.k5.train_r2, Test_R2 = rf.k5.test_r2
  ))
}
Growing trees.. Progress: 12%. Estimated remaining time: 3 minutes, 47 seconds.
Growing trees.. Progress: 25%. Estimated remaining time: 3 minutes, 8 seconds.
Growing trees.. Progress: 37%. Estimated remaining time: 2 minutes, 40 seconds.
Growing trees.. Progress: 50%. Estimated remaining time: 2 minutes, 5 seconds.
Growing trees.. Progress: 63%. Estimated remaining time: 1 minute, 31 seconds.
Growing trees.. Progress: 77%. Estimated remaining time: 56 seconds.
Growing trees.. Progress: 90%. Estimated remaining time: 24 seconds.
Error: vector memory exhausted (limit reached?)

K fold using K=5 and Boosting:

#### Boosting applying Cross Validation with k=5  ####

# Initialize lists to store models and their results
xgb.k5.models <- list()
xgb.k5.results <- data.frame()

# Perform k-fold cross-validation
for(i in seq_along(folds)) {
  # Split the data into training and testing for the current fold
  train_indices <- folds[[i]]
  test_indices <- setdiff(seq_len(nrow(train_data)), train_indices)
  
  train_data_fold <- train_data[train_indices, ]
  test_data_fold <- train_data[test_indices, ]
  
  # Prepare data for xgboost
  xgb.y_train_fold <- train_data_fold$int_rate
  xgb.X_train_fold <- as.matrix(train_data_fold[, -which(names(train_data_fold) == 'int_rate')])
  
  xgb.y_test_fold <- test_data_fold$int_rate
  xgb.X_test_fold <- as.matrix(test_data_fold[, -which(names(test_data_fold) == 'int_rate')])
  
  # Fit the xgboost model on the training fold
  xgb.k5 <- xgboost(
    data = xgb.X_train_fold,
    label = xgb.y_train_fold,
    nrounds = 100,
    verbose = 0
  )
  xgb.k5.models[[i]] <- xgb.k5  # Store the model
  
  # Make predictions on the training fold
  xgb.k5.train_predictions <- predict(xgb.k5, newdata = xgb.X_train_fold)
  # Make predictions on the testing fold
  xgb.k5.test_predictions <- predict(xgb.k5, newdata = xgb.X_test_fold)
  
  # Calculate metrics for training fold
  xgb.k5.train_mse <- mean((xgb.k5.train_predictions - train_data_fold$int_rate)^2)
  xgb.k5.train_rmse <- sqrt(xgb.k5.train_mse)
  xgb.k5.train_mae <- mean(abs(xgb.k5.train_predictions - train_data_fold$int_rate))
  xgb.k5.train_r2 <- 1 - (sum((xgb.y_train_fold - xgb.k5.train_predictions)^2) / sum((xgb.y_train_fold - mean(xgb.y_train_fold))^2))

  # Calculate metrics for testing fold
  xgb.k5.test_mse <- mean((xgb.k5.test_predictions - xgb.y_test_fold)^2)
  xgb.k5.test_rmse <- sqrt(xgb.k5.test_mse)
  xgb.k5.test_mae <- mean(abs(xgb.k5.test_predictions - xgb.y_test_fold))
  xgb.k5.test_r2 <- 1 - (sum((xgb.y_test_fold - xgb.k5.test_predictions)^2) / sum((xgb.y_test_fold - mean(xgb.y_test_fold))^2))  
  
  # Store metrics in the results dataframe
  xgb.k5.results <- rbind(xgb.k5.results, data.frame(
    Fold = i,
    Train_MSE = xgb.k5.train_mse, Test_MSE = xgb.k5.test_mse,
    Train_RMSE = xgb.k5.train_rmse, Test_RMSE = xgb.k5.test_rmse,
    Train_MAE = xgb.k5.train_mae, Test_MAE = xgb.k5.test_mae,
    Train_R2 = xgb.k5.train_r2, Test_R2 = xgb.k5.test_r2
  ))
}

# Display the models and their metrics
print(xgb.k5.models)
[[1]]
##### xgb.Booster
raw: 445.5 Kb 
call:
  xgb.train(params = params, data = dtrain, nrounds = nrounds, 
    watchlist = watchlist, verbose = verbose, print_every_n = print_every_n, 
    early_stopping_rounds = early_stopping_rounds, maximize = maximize, 
    save_period = save_period, save_name = save_name, xgb_model = xgb_model, 
    callbacks = callbacks)
params (as set within xgb.train):
  validate_parameters = "TRUE"
xgb.attributes:
  niter
callbacks:
  cb.evaluation.log()
# of features: 40 
niter: 100
nfeatures : 40 
evaluation_log:

[[2]]
##### xgb.Booster
raw: 458.1 Kb 
call:
  xgb.train(params = params, data = dtrain, nrounds = nrounds, 
    watchlist = watchlist, verbose = verbose, print_every_n = print_every_n, 
    early_stopping_rounds = early_stopping_rounds, maximize = maximize, 
    save_period = save_period, save_name = save_name, xgb_model = xgb_model, 
    callbacks = callbacks)
params (as set within xgb.train):
  validate_parameters = "TRUE"
xgb.attributes:
  niter
callbacks:
  cb.evaluation.log()
# of features: 40 
niter: 100
nfeatures : 40 
evaluation_log:

[[3]]
##### xgb.Booster
raw: 445.7 Kb 
call:
  xgb.train(params = params, data = dtrain, nrounds = nrounds, 
    watchlist = watchlist, verbose = verbose, print_every_n = print_every_n, 
    early_stopping_rounds = early_stopping_rounds, maximize = maximize, 
    save_period = save_period, save_name = save_name, xgb_model = xgb_model, 
    callbacks = callbacks)
params (as set within xgb.train):
  validate_parameters = "TRUE"
xgb.attributes:
  niter
callbacks:
  cb.evaluation.log()
# of features: 40 
niter: 100
nfeatures : 40 
evaluation_log:

[[4]]
##### xgb.Booster
raw: 454.4 Kb 
call:
  xgb.train(params = params, data = dtrain, nrounds = nrounds, 
    watchlist = watchlist, verbose = verbose, print_every_n = print_every_n, 
    early_stopping_rounds = early_stopping_rounds, maximize = maximize, 
    save_period = save_period, save_name = save_name, xgb_model = xgb_model, 
    callbacks = callbacks)
params (as set within xgb.train):
  validate_parameters = "TRUE"
xgb.attributes:
  niter
callbacks:
  cb.evaluation.log()
# of features: 40 
niter: 100
nfeatures : 40 
evaluation_log:

[[5]]
##### xgb.Booster
raw: 454.7 Kb 
call:
  xgb.train(params = params, data = dtrain, nrounds = nrounds, 
    watchlist = watchlist, verbose = verbose, print_every_n = print_every_n, 
    early_stopping_rounds = early_stopping_rounds, maximize = maximize, 
    save_period = save_period, save_name = save_name, xgb_model = xgb_model, 
    callbacks = callbacks)
params (as set within xgb.train):
  validate_parameters = "TRUE"
xgb.attributes:
  niter
callbacks:
  cb.evaluation.log()
# of features: 40 
niter: 100
nfeatures : 40 
evaluation_log:
print(xgb.k5.results)

Decision Tree

#### Decision Trees ####

# Error in tree: "factor predictors must have at most 32 levels" is thrown.
# Basically, it becomes computationally expensive to create so many splits in your data, since you are selecting the best split out of all 2^32 (approx) possible splits.


# Fit a decision tree model on the training data
#tm <- tree(int_rate ~ ., data = train_data)

# Make predictions on the training and testing data
#tm.train_predictions <- predict(tm, newdata = train_data)
#tm.test_predictions <- predict(tm, newdata = test_data)

# Calculate Mean Squared Error (MSE) for training and testing
#tm.train_mse <- mean((tm.train_predictions - train_data$int_rate)^2)
#tm.test_mse <- mean((tm.test_predictions - test_data$int_rate)^2)

# Calculate Root Mean Squared Error (RMSE) for training and testing
#tm.train_rmse <- sqrt(tm.train_mse)
#tm.test_rmse <- sqrt(tm.test_mse)

# Calculate Mean Absolute Error (MAE) for training and testing
#tm.train_mae <- mean(abs(tm.train_predictions - train_data$int_rate))
#tm.test_mae <- mean(abs(tm.test_predictions - test_data$int_rate))

# Calculate R-squared (R²) for training and testing
#tm.train_r2 <- 1 - (sum((train_data$int_rate - tm.train_predictions)^2) / sum((train_data$int_rate - mean(train_data$int_rate))^2))
#tm.test_r2 <- 1 - (sum((test_data$int_rate - tm.test_predictions)^2) / sum((test_data$int_rate - mean(test_data$int_rate))^2))

# Display the metrics
#cat("Training MSE:", tm.train_mse, "\n")
#cat("Testing MSE:", tm.test_mse, "\n")
#cat("Training RMSE:", tm.train_rmse, "\n")
#cat("Testing RMSE:", tm.test_rmse, "\n")
#cat("Training MAE:", tm.train_mae, "\n")
#cat("Testing MAE:", tm.test_mae, "\n")
#cat("Training R-squared (R²):", tm.train_r2, "\n")
#cat("Testing R-squared (R²):", tm.test_r2, "\n")

Random Forest

#### Random Forest ####

# Train a Random Forest model
rf <- ranger(formula = int_rate ~ ., data = train_data, num.trees = 500, verbose=TRUE, importance = "impurity", oob.error = TRUE)
Growing trees.. Progress: 11%. Estimated remaining time: 4 minutes, 8 seconds.
Growing trees.. Progress: 24%. Estimated remaining time: 3 minutes, 20 seconds.
Growing trees.. Progress: 37%. Estimated remaining time: 2 minutes, 41 seconds.
Growing trees.. Progress: 50%. Estimated remaining time: 2 minutes, 8 seconds.
Growing trees.. Progress: 63%. Estimated remaining time: 1 minute, 34 seconds.
Growing trees.. Progress: 75%. Estimated remaining time: 1 minute, 2 seconds.
Growing trees.. Progress: 88%. Estimated remaining time: 30 seconds.
# Print the model summary
print("Random Forest Model Summary:")
[1] "Random Forest Model Summary:"
print(rf)
Ranger result

Call:
 ranger(formula = int_rate ~ ., data = train_data, num.trees = 500,      verbose = TRUE, importance = "impurity", oob.error = TRUE) 

Type:                             Regression 
Number of trees:                  500 
Sample size:                      638392 
Number of independent variables:  40 
Mtry:                             6 
Target node size:                 5 
Variable importance mode:         impurity 
Splitrule:                        variance 
OOB prediction error (MSE):       8.744647 
R squared (OOB):                  0.5446644 
# Make predictions on the training and testing data
rf.train_predictions <- predict(rf, data = train_data)
Predicting.. Progress: 81%. Estimated remaining time: 7 seconds.
rf.test_predictions <- predict(rf, data = test_data)

# Calculate Mean Squared Error (MSE) for training and testing
rf.train_mse <- mean((rf.train_predictions$predictions - train_data$int_rate)^2)
rf.test_mse <- mean((rf.test_predictions$predictions - test_data$int_rate)^2)

# Calculate Root Mean Squared Error (RMSE) for training and testing
rf.train_rmse <- sqrt(rf.train_mse)
rf.test_rmse <- sqrt(rf.test_mse)

# Calculate Mean Absolute Error (MAE) for training and testing
rf.train_mae <- mean(abs(rf.train_predictions$predictions - train_data$int_rate))
rf.test_mae <- mean(abs(rf.test_predictions$predictions - test_data$int_rate))

# Calculate R-squared (R²) for training and testing
rf.train_r2 <- 1 - (sum((train_data$int_rate - rf.train_predictions$predictions)^2) / sum((train_data$int_rate - mean(train_data$int_rate))^2))
rf.test_r2 <- 1 - (sum((test_data$int_rate - rf.test_predictions$predictions)^2) / sum((test_data$int_rate - mean(test_data$int_rate))^2))

# Display the metrics
cat("Training MSE:", rf.train_mse, "\n")
Training MSE: 2.066856 
cat("Testing MSE:", rf.test_mse, "\n")
Testing MSE: 8.69323 
cat("Training RMSE:", rf.train_rmse, "\n")
Training RMSE: 1.437656 
cat("Testing RMSE:", rf.test_rmse, "\n")
Testing RMSE: 2.948428 
cat("Training MAE:", rf.train_mae, "\n")
Training MAE: 1.130191 
cat("Testing MAE:", rf.test_mae, "\n")
Testing MAE: 2.331673 
cat("Training R-squared (R²):", rf.train_r2, "\n")
Training R-squared (R²): 0.8923782 
cat("Testing R-squared (R²):", rf.test_r2, "\n")
Testing R-squared (R²): 0.5471326 
#rf <- randomForest(int_rate~., data=train_data, ntree = 5, mtry = 3)
#bag.boston=randomForest(medv~.,data=Boston,subset=train, mtry=13,importance =TRUE)
#print(rf)

# Set the number of cores you want to use
#num_cores <- 6  # Adjust this number based on your system's capabilities

# Register parallel backend
#cl <- makeCluster(num_cores)
#registerDoParallel(cl)

# Assuming 'lc_data' is your dataset
#rf_model <- foreach(ntree = rep(100, num_cores), .packages = 'randomForest') %dopar% {
#    randomForest(int_rate ~ ., data = lc_data, ntree = ntree, mtry = sqrt(ncol(lc_data)))
#}

# After training, stop the cluster to release the cores:
#stopCluster(cl)

Boosting

#### Boosting ####

# Define the target variable for training and testing
xgb.y_train <- train_data$int_rate
xgb.y_test <- test_data$int_rate  # Use test_data for testing

# Define the feature matrix for training and testing (exclude the target variable)
xgb.X_train <- train_data[, -which(names(train_data) == 'int_rate')]
xgb.X_test <- test_data[, -which(names(test_data) == 'int_rate')]  # Use test_data for testing

# Fit a gradient boosting regression model using xgboost
xgb <- xgboost(
  data = as.matrix(xgb.X_train),
  label = xgb.y_train,
  nrounds = 100,
  verbose = 0
)

# Make predictions on the training and testing data
xgb.train_predictions <- predict(xgb, newdata = as.matrix(xgb.X_train))
xgb.test_predictions <- predict(xgb, newdata = as.matrix(xgb.X_test))

# Calculate Mean Squared Error (MSE) for training and testing
xgb.train_mse <- mean((xgb.train_predictions - xgb.y_train)^2)
xgb.test_mse <- mean((xgb.test_predictions - xgb.y_test)^2)

# Calculate Root Mean Squared Error (RMSE) for training and testing
xgb.train_rmse <- sqrt(xgb.train_mse)
xgb.test_rmse <- sqrt(xgb.test_mse)

# Calculate Mean Absolute Error (MAE) for training and testing
xgb.train_mae <- mean(abs(xgb.train_predictions - xgb.y_train))
xgb.test_mae <- mean(abs(xgb.test_predictions - xgb.y_test))

# Calculate R-squared (R²) for training and testing
xgb.train_r2 <- 1 - (sum((xgb.y_train - xgb.train_predictions)^2) / sum((xgb.y_train - mean(xgb.y_train))^2))
xgb.test_r2 <- 1 - (sum((xgb.y_test - xgb.test_predictions)^2) / sum((xgb.y_test - mean(xgb.y_test))^2))

# Display the metrics
cat("Training MSE:", xgb.train_mse, "\n")
Training MSE: 7.70773 
cat("Testing MSE:", xgb.test_mse, "\n")
Testing MSE: 8.007011 
cat("Training RMSE:", xgb.train_rmse, "\n")
Training RMSE: 2.77628 
cat("Testing RMSE:", xgb.test_rmse, "\n")
Testing RMSE: 2.829666 
cat("Training MAE:", xgb.train_mae, "\n")
Training MAE: 2.179231 
cat("Testing MAE:", xgb.test_mae, "\n")
Testing MAE: 2.219719 
cat("Training R-squared (R²):", xgb.train_r2, "\n")
Training R-squared (R²): 0.5986562 
cat("Testing R-squared (R²):", xgb.test_r2, "\n")
Testing R-squared (R²): 0.5828808 

Following, a scatter plot of actual vs predicted training values for each model is plot. This plot helps us visualize how well each model’s predictions align with the actual data points.

# Create a scatter plot function
create_scatter_plot <- function(actual_values, predicted_values, model_name) {
  model_comparison_data <- data.frame(
    Actual = actual_values,
    Predicted = predicted_values
  )
  
  scatter_plot <- ggplot(model_comparison_data, aes(x = Actual, y = Predicted)) +
    geom_point() +
    geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") +  # Add a diagonal reference line
    labs(x = "Actual Training Values", y = "Predicted Training Values", title = model_name) +
    theme_minimal() +
    ylim(-50, 50)
  
  return(scatter_plot)
}

# Create scatter plots for each model
lm_scatter_plot <- create_scatter_plot(
  actual_values = train_data$int_rate,
  predicted_values = lm.train_predictions,
  model_name = "Linear Regression"
)

rf_scatter_plot <- create_scatter_plot(
  actual_values = train_data$int_rate,
  predicted_values = rf.train_predictions$predictions,
  model_name = "Random Forest"
)

xgb_scatter_plot <- create_scatter_plot(
  actual_values = xgb.y_train,
  predicted_values = xgb.train_predictions,
  model_name = "XGBoost"
)

# Display the scatter plots separately
print(lm_scatter_plot)

print(rf_scatter_plot)

print(xgb_scatter_plot)

Following, a scatter plot of actual vs predicted testing values for each model is plot. This plot helps us visualize how well each model’s predictions align with the actual data points.

# Create a scatter plot function
create_scatter_plot <- function(actual_values, predicted_values, model_name) {
  model_comparison_data <- data.frame(
    Actual = actual_values,
    Predicted = predicted_values
  )
  
  scatter_plot <- ggplot(model_comparison_data, aes(x = Actual, y = Predicted)) +
    geom_point() +
    geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") +  # Add a diagonal reference line
    labs(x = "Actual Testing Values", y = "Predicted Testing Values", title = model_name) +
    theme_minimal() +
    ylim(-50, 50) +
    xlim(0, 40)
  
  return(scatter_plot)
}

# Create scatter plots for each model
lm_scatter_plot <- create_scatter_plot(
  actual_values = test_data$int_rate,
  predicted_values = lm.test_predictions,
  model_name = "Linear Regression"
)

rf_scatter_plot <- create_scatter_plot(
  actual_values = test_data$int_rate,
  predicted_values = rf.test_predictions$predictions,
  model_name = "Random Forest"
)

xgb_scatter_plot <- create_scatter_plot(
  actual_values = xgb.y_test,
  predicted_values = xgb.test_predictions,
  model_name = "XGBoost"
)

# Display the scatter plots separately
print(lm_scatter_plot)

print(rf_scatter_plot)

print(xgb_scatter_plot)

Residual plots can help identify patterns in prediction errors and assess whether the assumptions of linear regression (if applicable) are met.

# Create a residual plot function
create_residual_plot <- function(actual_values, predicted_values, model_name) {
  residuals <- actual_values - predicted_values
  residual_data <- data.frame(
    Predicted = predicted_values,
    Residuals = residuals
  )
  
  residual_plot <- ggplot(residual_data, aes(x = Predicted, y = Residuals)) +
    geom_point() +
    geom_hline(yintercept = 0, linetype = "dashed", color = "red") +  # Red horizontal reference line
    labs(x = "Predicted Values", y = "Residuals", title = paste("Residual Plot -", model_name)) +
    theme_minimal() +
    ylim(-30, 30) +
    xlim(0, 40)
  
  return(residual_plot)
}

# Create residual plots for each model
lm_residual_plot <- create_residual_plot(
  actual_values = train_data$int_rate,
  predicted_values = lm.train_predictions,
  model_name = "Linear Regression"
)

rf_residual_plot <- create_residual_plot(
  actual_values = train_data$int_rate,
  predicted_values = rf.train_predictions$predictions,
  model_name = "Random Forest"
)

xgb_residual_plot <- create_residual_plot(
  actual_values = xgb.y_train,
  predicted_values = xgb.train_predictions,
  model_name = "XGBoost"
)

# Display the residual plots separately
print(lm_residual_plot)

print(rf_residual_plot)

print(xgb_residual_plot)

From the plots above we can clearly see that:

This visualization can help you compare the distribution of prediction errors across models.

# Create a density plot function for residuals
create_residual_density_plot <- function(actual_values, predicted_values, model_name) {
  residuals <- actual_values - predicted_values
  residual_data <- data.frame(Residuals = residuals)
  
  density_plot <- ggplot(residual_data, aes(x = Residuals)) +
    geom_density(fill = "skyblue", color = "black", alpha = 0.7) +
    labs(x = "Residuals", y = "Density", title = paste("Residual Density Plot -", model_name)) +
    theme_minimal() +
    xlim(-30,30) + 
    ylim(0, 0.35)
    
  
  return(density_plot)
}

# Create density plots for residuals for each model
lm_residual_density_plot <- create_residual_density_plot(
  actual_values = train_data$int_rate,
  predicted_values = lm.train_predictions,
  model_name = "Linear Regression"
)

rf_residual_density_plot <- create_residual_density_plot(
  actual_values = train_data$int_rate,
  predicted_values = rf.train_predictions$predictions,
  model_name = "Random Forest"
)

xgb_residual_density_plot <- create_residual_density_plot(
  actual_values = xgb.y_train,
  predicted_values = xgb.train_predictions,
  model_name = "XGBoost"
)

# Display the density plots separately
print(lm_residual_density_plot)

print(rf_residual_density_plot)

print(xgb_residual_density_plot)

This visualization can help you compare the distribution of prediction errors across models through histograms.

# Create a histogram plot function for residuals with a red density curve
create_residual_histogram_plot <- function(actual_values, predicted_values, model_name) {
  residuals <- actual_values - predicted_values
  residual_data <- data.frame(Residuals = residuals)
  
  histogram_plot <- ggplot(residual_data, aes(x = Residuals)) +
    geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "skyblue", color = "black", alpha = 0.7) +  # Use density on the y-axis for the histogram
    geom_density(color = "red", linewidth = 1.5) +  # Add the density plot in red
    labs(x = "Residuals", y = "Density", title = paste("Residual Histogram Plot with Density Curve -", model_name)) +
    theme_minimal() +
    xlim(-20,20) + 
    ylim(0, 0.3)
  
  return(histogram_plot)
}

# Create histogram plots for residuals for each model
lm_residual_histogram_plot <- create_residual_histogram_plot(
  actual_values = train_data$int_rate,
  predicted_values = lm.train_predictions,
  model_name = "Linear Regression"
)

rf_residual_histogram_plot <- create_residual_histogram_plot(
  actual_values = train_data$int_rate,
  predicted_values = rf.train_predictions$predictions,
  model_name = "Random Forest"
)

xgb_residual_histogram_plot <- create_residual_histogram_plot(
  actual_values = xgb.y_train,
  predicted_values = xgb.train_predictions,
  model_name = "XGBoost"
)

# Display the histogram plots separately
print(lm_residual_histogram_plot)

print(rf_residual_histogram_plot)

print(xgb_residual_histogram_plot)

For each model a bar chart that displays the R-squared (coefficient of determination) values is created. R-squared measures the proportion of variance in the target variable explained by the model. Higher R-squared values indicate better model fit.

# Create a data frame with R-squared values for each model
model_names <- c("Linear Regression", "Random Forest", "XGBoost")
r_squared_values_train <- c(
  lm.train_r2,
  rf.train_r2,
  xgb.train_r2
)
r_squared_values_test <- c(
  lm.test_r2,
  rf.test_r2,
  xgb.test_r2
)

r_squared_data_train <- data.frame(Model = factor(model_names),
                              R_squared = r_squared_values_train)
r_squared_data_test <- data.frame(Model = factor(model_names),
                              R_squared = r_squared_values_test)

# Create the R-squared comparison bar chart
r_squared_bar_chart_train <- ggplot(r_squared_data_train, aes(x = Model, y = R_squared, fill = Model)) +
  geom_bar(stat = "identity") +
  labs(x = "Model", y = "R-squared (R²)", title = "R-squared Comparison Training") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylim(0,1)
r_squared_bar_chart_test <- ggplot(r_squared_data_test, aes(x = Model, y = R_squared, fill = Model)) +
  geom_bar(stat = "identity") +
  labs(x = "Model", y = "R-squared (R²)", title = "R-squared Comparison Testing") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylim(0,1)

# Display the R-squared comparison bar chart
print(r_squared_bar_chart_train)

print(r_squared_bar_chart_test)

A bar chart that compares the MAE or RMSE values, is generated for each model. These metrics quantify the average prediction errors of each model, and lower values are preferred.

# Create a data frame with MAE and RMSE values for each model
model_names <- c("Linear Regression", "Random Forest", "XGBoost","Linear Regression", "Random Forest", "XGBoost")
error_values_train <- c(
  lm.train_mae,
  rf.train_mae,
  xgb.train_mae,
  lm.train_rmse,
  rf.train_rmse,
  xgb.train_rmse
)
error_values_test <- c(
  lm.test_mae,
  rf.test_mae,
  xgb.test_mae,
  lm.test_rmse,
  rf.test_rmse,
  xgb.test_rmse
)
error_type <- c(
  "MAE", "MAE", "MAE","RMSE","RMSE","RMSE"
)
model_errors_train <- data.frame(Model = factor(model_names, levels = c("Linear Regression", "Random Forest", "XGBoost")),
                Error = error_values_train, Type = error_type)
model_errors_test <- data.frame(Model = factor(model_names, levels = c("Linear Regression", "Random Forest", "XGBoost")),
                Error = error_values_test, Type = error_type)
# Create the MAE or RMSE comparison bar chart
error_bar_chart_train <- ggplot(model_errors_train, aes(x = Model, y = Error, fill = Type)) +
  geom_bar(stat = "identity", position = "dodge") +
  labs(x = "Model", y = "Error Value", title = "Training MAE and RMSE Comparison") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylim(0, 4)

error_bar_chart_test <- ggplot(model_errors_test, aes(x = Model, y = Error, fill = Type)) +
  geom_bar(stat = "identity", position = "dodge") +
  labs(x = "Model", y = "Error Value", title = "Testing MAE and RMSE Comparison") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylim(0, 4)

# Display the MAE and RMSE comparison bar chart
print(error_bar_chart_train)

print(error_bar_chart_test)

#### Random Forest Feature Importance Plot ####
v1 <- vip(rf, title = "Ranger", num_features = 20) 
plot(v1)

Learning curve using RMSE and R^2:

# TODO: change the x-axes
# Create a data frame with RMSE and R-squared values for each model and sample size
model_names <- c("Linear Regression", "Random Forest", "XGBoost")
sample_sizes <- seq(10, nrow(train_data), by = 10)  # Adjust the sample sizes as needed

# Create data frames with RMSE and R-squared values for each model
rmse_data <- data.frame(
  Model = rep(model_names, each = length(sample_sizes)),
  Sample_Size = rep(sample_sizes, times = length(model_names)),
  RMSE = c(
    lm.train_rmse, rf.train_rmse, xgb.train_rmse
  )
)

r_squared_data <- data.frame(
  Model = rep(model_names, each = length(sample_sizes)),
  Sample_Size = rep(sample_sizes, times = length(model_names)),
  R_squared = c(
    lm.train_r2, rf.train_r2, xgb.train_r2
  )
)

# Create RMSE learning curve
rmse_curve <- ggplot(rmse_data, aes(x = Sample_Size, y = RMSE, color = Model)) +
  geom_line() +
  labs(x = "Sample Size", y = "RMSE", title = "RMSE Learning Curve") +
  theme_minimal()

# Create R-squared learning curve
r_squared_curve <- ggplot(r_squared_data, aes(x = Sample_Size, y = R_squared, color = Model)) +
  geom_line() +
  labs(x = "Sample Size", y = "R-squared", title = "R-squared Learning Curve") +
  theme_minimal()

# Display the RMSE and R-squared learning curves
print(rmse_curve)

print(r_squared_curve)

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBEYXRhIFByZS1wcm9jZXNzaW5nCgpMb2FkIG5lZWRlZCBsaWJyYXJpZXMKCmBgYHtyfQpsaWJyYXJ5KGZhc3REdW1taWVzKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KGJvb3QpCmxpYnJhcnkodHJlZSkKbGlicmFyeShyYW5nZXIpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShnYm0pCmxpYnJhcnkodmlwKQpsaWJyYXJ5KElTTFIpCmxpYnJhcnkodGlkeXIpCmBgYAoKU2V0IHRoZSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKCmBgYHtyfQpzZXQuc2VlZCgxKQpgYGAKCkxvYWQgdGhlIGRhdGFzZXQKCmBgYHtyfQpvcmlnaW5hbF9sY19kYXRhIDwtIHJlYWQuY3N2KCJMQ2RhdGEuY3N2IixzZXAgPSAiOyIpCmxjX2RhdGEgPC0gb3JpZ2luYWxfbGNfZGF0YQpgYGAKCnJlbW92ZSBhdHRyaWJ1dGVzIG5vdCBhdmFpbGFibGUgZm9yIHByZWRpY3Rpb24KCmBgYHtyfQpsY19kYXRhIDwtIHN1YnNldChsY19kYXRhLCBzZWxlY3QgPSAtYyhjb2xsZWN0aW9uX3JlY292ZXJ5X2ZlZSwgaW5zdGFsbG1lbnQsIGlzc3VlX2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RfcHltbnRfYW1udCwgbGFzdF9weW1udF9kLCBsb2FuX3N0YXR1cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV4dF9weW1udF9kLCBvdXRfcHJuY3AsIG91dF9wcm5jcF9pbnYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB5bW50X3BsYW4sIHJlY292ZXJpZXMsIHRvdGFsX3B5bW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9weW1udF9pbnYsdG90YWxfcmVjX2ludCwgdG90YWxfcmVjX2xhdGVfZmVlLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfcmVjX3BybmNwKSkKCmBgYAoKYGBge3J9CnN1bW1hcnkobGNfZGF0YSkKYGBgCgpGaXJzdCB3ZSBkZWxldGUgdGhlIGNvbHVtbnMgd2hpY2ggYXJlbid0IHVzZWZ1bCBmb3Igb3VyIHByZWRpY3Rpb24KCmBgYHtyfQpsY19kYXRhJGlkIDwtIE5VTEwKbGNfZGF0YSRtZW1iZXJfaWQgPC0gTlVMTApsY19kYXRhJHppcF9jb2RlIDwtIE5VTEwKbGNfZGF0YSR1cmwgPC0gTlVMTApgYGAKTG9va3MgbGlrZSAqKnBvbGljeV9jb2RlKiogY29udGFpbnMganVzdCB2YWx1ZSBlcXVhbCB0byAxLCBpdCBjYW4gYmUgcmVtb3ZlZApgYGB7cn0KbGNfZGF0YSRwb2xpY3lfY29kZSA8LSBOVUxMCmBgYApSZW1vdmUgYWRkaXRpb25hbCBjb2x1bW5zIHdoaWNoIGFyZSByZWxhdGVkIHRvIHRoZSBoaXN0b3JpY2FsIGRhdGEKYGBge3J9CmxjX2RhdGEkbGFzdF9jcmVkaXRfcHVsbF9kIDwtIE5VTEwKYGBgCgpUaGVuIHdlIGRlbGV0ZSB0aGUgY29sdW1ucyB3aGljaCBjYW4ndCBiZSBjb252ZXJ0ZWQgdG8gY2F0ZWdvcmljYWwgYW5kIHJlcXVpcmUgTkxQCgpgYGB7cn0KbGNfZGF0YSR0aXRsZSA8LSBOVUxMCmxjX2RhdGEkZGVzYyA8LSBOVUxMCmxjX2RhdGEkZW1wX3RpdGxlIDwtIE5VTEwKCmBgYAoKbGV0J3MgZXhhbWluZSB0aGUgKipsb2FuX2FtbnQqKiBjb2x1bW4KCmBgYHtyfQpzdW0oaXMubmEobGNfZGF0YSRsb2FuX2FtbnQpKQpjb3IobGNfZGF0YSRsb2FuX2FtbnQsIGxjX2RhdGEkaW50X3JhdGUpCmhpc3QobGNfZGF0YSRsb2FuX2FtbnQsIGJyZWFrcyA9IDIwLCBtYWluID0gImxvYW5fYW1udCBkaXN0cmlidXRpb24iLCB4bGFiID0gImxvYW5fYW1udCIsIGNvbCA9ICJsaWdodGJsdWUiLCBib3JkZXIgPSAiYmxhY2siKQpnZ3Bsb3QoZGF0YSA9IGxjX2RhdGEsIG1hcHBpbmcgPSBhZXMoeD1pbnRfcmF0ZSx5PWxvYW5fYW1udCkpICsgZ2VvbV9ib3hwbG90KCkKYGBgCgpzdGFuZGFyZGl6ZSAqKmxvYW5fYW1udCoqCgpgYGB7cn0KI2xjX2RhdGEkbG9hbl9hbW50IDwtIHNjYWxlKGxjX2RhdGEkbG9hbl9hbW50KQpgYGAKCmxldCdzIGV4YW1pbmUgdGhlICoqZnVuZGVkX2FtbnQqKiBjb2x1bW4KCmBgYHtyfQpzdW0oaXMubmEobGNfZGF0YSRmdW5kZWRfYW1udCkpCmNvcihsY19kYXRhJGZ1bmRlZF9hbW50LCBsY19kYXRhJGludF9yYXRlKQpoaXN0KGxjX2RhdGEkZnVuZGVkX2FtbnQsIGJyZWFrcyA9IDIwLCBtYWluID0gImZ1bmRlZF9hbW50IGRpc3RyaWJ1dGlvbiIsIHhsYWIgPSAiZnVuZGVkX2FtbnQiLCBjb2wgPSAibGlnaHRibHVlIiwgYm9yZGVyID0gImJsYWNrIikKYGBgCgphcyB3ZSBjYW4gc2VlLCAqKmZ1bmRlZF9hbW50KiogaXMgYWxtb3N0IHRoZSBzYW1lIGFzIHRoZSAqKmxvYW5fYW1udCoqIGNvbHVtbiwgY29uc2VxdWVudGx5LCB3ZSByZW1vdmUgaXQuCgpgYGB7cn0KbGNfZGF0YSRmdW5kZWRfYW1udCA8LSBOVUxMIApgYGAKCmxldCdzIGV4YW1pbmUgdGhlICoqZnVuZGVkX2FtbnRfaW52KiogY29sdW1uCgpgYGB7cn0Kc3VtKGlzLm5hKGxjX2RhdGEkZnVuZGVkX2FtbnRfaW52KSkKY29yKGxjX2RhdGEkZnVuZGVkX2FtbnRfaW52LCBsY19kYXRhJGludF9yYXRlKQpoaXN0KGxjX2RhdGEkZnVuZGVkX2FtbnRfaW52LCBicmVha3MgPSAyMCwgbWFpbiA9ICJmdW5kZWRfYW1udF9pbnYgZGlzdHJpYnV0aW9uIiwgeGxhYiA9ICJmdW5kZWRfYW1udF9pbnYiLCBjb2wgPSAibGlnaHRibHVlIiwgYm9yZGVyID0gImJsYWNrIikKYGBgCgpyZW1vdmUgKipmdW5kZWRfYW1udF9pbnYqKiBmb3IgdGhlIHNhbWUgcmVhc29uIGFzIGFib3ZlCgpgYGB7cn0KbGNfZGF0YSRmdW5kZWRfYW1udF9pbnYgPC0gTlVMTApgYGAKCmxldCdzIHNlZSB0aGUgKippbnRfcmF0ZSoqIGRpc3RyaWJ1dGlvbi4KYGBge3J9Cmhpc3QobGNfZGF0YSRpbnRfcmF0ZSwgYnJlYWtzID0gMjAsIG1haW4gPSAiaW50X3JhdGUgZGlzdHJpYnV0aW9uIiwgeGxhYiA9ICJpbnRfcmF0ZSIsIGNvbCA9ICJsaWdodGJsdWUiLCBib3JkZXIgPSAiYmxhY2siKQpgYGAKU3RhbmRhcmRpemUgaW50IHJhdGU6CmBgYHtyfQojbGNfZGF0YSRpbnRfcmF0ZSA8LSBzY2FsZShsY19kYXRhJGludF9yYXRlKQpgYGAKd2UgZGVsZXRlIHRoZSAqKmVtcF90aXRsZSoqIGNvbHVtbiBhcyB0aGVyZSBhcmUgc2V2ZXJhbCBlbnRyaWVzIGZvciB0aGUgc2FtZSBqb2IgdGl0bGUgYW5kIGJlY2F1c2UgdGhlcmUgYXJlIHRvbyBtYW55IGRpZmZlcmVudCB2YWx1ZXMgZm9yIG9uZS1ob3QgZW5jb2RpbmcuIEluIGFkZGl0aW9uLCBzb21lIHRpdGxlcyBhcmUgdW5jbGVhciAoTkxQIHJlcXVpcmVkKQpgYGB7cn0Kbl9kaXN0aW5jdChsY19kYXRhJGVtcF90aXRsZSkKYGBgCkFzIHdlIGNhbiBvYnNlcnZlLCB0aGVyZSBhcmUgNDAzNjMgTkFzLiBXZSBjYW4gYXNzdW1lIDQwMzYzIGRvIG5vdCB3b3JrLgpgYGB7cn0KYmFycGxvdCh0YWJsZShsY19kYXRhJGVtcF9sZW5ndGgpLAogICAgICAgIHhsYWIgPSAiZW1wX2xlbmd0aCB5ZWFycyIsIAogICAgICAgIHlsYWIgPSAiRnJlcXVlbmN5IiwgCiAgICAgICAgY29sID0gInNreWJsdWUiLCAKICAgICAgICBib3JkZXIgPSAiYmxhY2siLAogICAgICAgIGNleC5uYW1lcyA9IDAuNikgICMgVGhlIHNpemUgb2YgdGhlIG1haW4gdGl0bGUKYGBgCgpTaW5jZSAqKmVtcF9sZW5ndGgqKiBzZWVtcyB0byBiZSBjYXRlZ29yaWNhbCwgd2UgdHJhbnNmb3JtIGl0IHRvIGFzIGEgZmFjdG9yIGFuZCB0aGVuIGFzIG51bWVyaWMuClRoZSBjb252ZXJzaW9uIHRvIG51bWVyaWMgaXMgbmVlZGVkIGZvciBzdXBwb3J0aW5nIHRoZSBYR0Jvb3N0IApgYGB7cn0KbGNfZGF0YSRlbXBfbGVuZ3RoIDwtIGFzLmZhY3RvcihsY19kYXRhJGVtcF9sZW5ndGgpCmdncGxvdChkYXRhID0gbGNfZGF0YSwgbWFwcGluZyA9IGFlcyh4PWludF9yYXRlLHk9ZW1wX2xlbmd0aCkpICsgZ2VvbV9ib3hwbG90KCkKbGNfZGF0YSRlbXBfbGVuZ3RoIDwtIGFzLm51bWVyaWMobGNfZGF0YSRlbXBfbGVuZ3RoKQpgYGAKKip0ZXJtKioKYGBge3J9CmxjX2RhdGEkdGVybSA8LSBhcy5mYWN0b3IobGNfZGF0YSR0ZXJtKQpnZ3Bsb3QoZGF0YSA9IGxjX2RhdGEsIG1hcHBpbmcgPSBhZXMoeD1pbnRfcmF0ZSx5PXRlcm0pKSArIGdlb21fYm94cGxvdCgpCmxjX2RhdGEkdGVybSA8LSBhcy5udW1lcmljKGxjX2RhdGEkdGVybSkKYm9oID0gb3JkZXIobGNfZGF0YSR0ZXJtKQpgYGAKCkNsZWFuaW5nIG9mICoqaG9tZV9vd25lcnNoaXAqKjoKCkR1cmluZyB0aGUgZGF0YSBjbGVhbmluZyBwaGFzZSwgb3VyIGFuYWx5c2lzIHJldmVhbGVkIHRoYXQgdGhlIHZhcmlhYmxlICJob21lX293bmVyc2hpcCIgZG9lcyBub3Qgc2hvdyBhIGRpc3RpbmN0IGNvcnJlbGF0aW9uIHdpdGggaW50ZXJlc3QgcmF0ZXMuIFNwZWNpZmljYWxseSwgYW1vbmcgdGhlIGNhdGVnb3JpZXMsICJBTlkiIGFuZCAiT1RIRVIiIGNvbnRhaW4gMiBhbmQgMTU0IGNhc2VzLCByZXNwZWN0aXZlbHksIHdoaWxlIHRoZSAiTk9ORSIgY2F0ZWdvcnkgY29tcHJpc2VzIDM5IGNhc2VzLiBBbHRob3VnaCB0aGUgIk5PTkUiIGNhdGVnb3J5IGFwcGVhcnMgdG8gZGVtb25zdHJhdGUgYSBoaWdoZXIgaW50ZXJlc3QgcmF0ZSBjb21wYXJlZCB0byBvdGhlcnMsIHRoZSBsaW1pdGVkIHNhbXBsZSBzaXplIG9mIDM5IGNhc2VzIHJhaXNlcyBkb3VidHMgYWJvdXQgdGhlIHJlbGlhYmlsaXR5IG9mIHRoaXMgb2JzZXJ2YXRpb24uIE5vdGFibHksIHRoZSAiTk9ORSIgY2F0ZWdvcnkgbWlnaHQgcGVydGFpbiB0byBpbmRpdmlkdWFscyBleHBlcmllbmNpbmcgaG9tZWxlc3NuZXNzLCBwcm9tcHRpbmcgZXRoaWNhbCBjb25jZXJucyBhYm91dCBsb2FuIHByb3Zpc2lvbiB0byB0aGlzIGRlbW9ncmFwaGljLgoKYGBge3J9CnRhYmxlKGxjX2RhdGEkaG9tZV9vd25lcnNoaXApCmdncGxvdChkYXRhID0gbGNfZGF0YSwgbWFwcGluZyA9IGFlcyh4PWludF9yYXRlLHk9aG9tZV9vd25lcnNoaXApKSArIGdlb21fYm94cGxvdCgpCmBgYApUaGVuLCB3ZSByZXRhaW4gbW9ydGdhZ2UsIG93biBhbmQgcmVudDoKYGBge3J9CmxjX2RhdGEgPC0gbGNfZGF0YSAlPiUgZmlsdGVyKGhvbWVfb3duZXJzaGlwICVpbiUgYygiTU9SVEdBR0UiLCJPV04iLCJSRU5UIikpCmxjX2RhdGEkaG9tZV9vd25lcnNoaXAgPC0gYXMubnVtZXJpYyhhcy5mYWN0b3IobGNfZGF0YSRob21lX293bmVyc2hpcCkpCmBgYAoKIyBhcHBsaWNhdGlvbiBqb2ludCBoYW5kbGluZwpgYGB7cn0KCiMgbWVyZ2luZyBhbm51YWwgaW5jb21lCmxjX2RhdGEgPC0gbGNfZGF0YSAlPiUgbXV0YXRlKAogICAgYW5udWFsX2luY19tZXJnZWQgPSBpZmVsc2UoaXMubmEoYW5udWFsX2luY19qb2ludCk9PSBUUlVFLCBhbm51YWxfaW5jLGFubnVhbF9pbmNfam9pbnQpKSAKCmxjX2RhdGEgPC0gbGNfZGF0YSAlPiUgc2VsZWN0KC1hbm51YWxfaW5jLC1hbm51YWxfaW5jX2pvaW50KQoKCiMgbWVyZ2luZyBkZWJ0IHRvIGluY29tZSByYXRpbwpsY19kYXRhIDwtIGxjX2RhdGEgJT4lIG11dGF0ZSgKICAgIGR0aV9tZXJnZWQgPSBpZmVsc2UoaXMubmEoZHRpX2pvaW50KT09IFRSVUUsIGR0aSxkdGlfam9pbnQpKSAKCmxjX2RhdGEgPC0gbGNfZGF0YSAlPiUgc2VsZWN0KC1kdGksLWR0aV9qb2ludCkKCmBgYAoKVXBvbiByZXZpZXdpbmcgdGhlIHN1bW1hcnkgYWdhaW4sIGl0IGJlY29tZXMgYXBwYXJlbnQgdGhhdCB0aGVyZSBhcmUgbWVyZWx5IDQ2MCBqb2ludCBhcHBsaWNhdGlvbnMsIGNvbnN0aXR1dGluZyBhIHNtYWxsIHN1YnNldCB3aXRoaW4gdGhlIGV4dGVuc2l2ZSBkYXRhc2V0IG9mIGFyb3VuZCA4MDBrIHJvd3MuIFRocm91Z2ggY29uc29saWRhdGluZyB0aGUgZGVidC10by1pbmNvbWUgcmF0aW9zIChkdGkncyksIHdlIGNhbiBwaW5wb2ludCB0aGUgZGF0YSBwZXJ0aW5lbnQgdG8gb3VyIHJlc2VhcmNoIG9iamVjdGl2ZXMuIEhlbmNlLCBpdCBpcyBhZHZpc2FibGUgdG8gZWxpbWluYXRlIHRoZSBjb2x1bW5zIHZlcmlmaWNhdGlvbl9zdGF0dXNfam9pbnQgYW5kIGFwcGxpY2F0aW9uX3R5cGUgdG8gcHJldmVudCBpbnRyb2R1Y2luZyB1bndhcnJhbnRlZCB2YXJpYWJpbGl0eSBpbnRvIG91ciBhbmFseXNpcy4KCmBgYHtyfQp0YWJsZShsY19kYXRhJHZlcmlmaWNhdGlvbl9zdGF0dXMpCnRhYmxlKGxjX2RhdGEkdmVyaWZpY2F0aW9uX3N0YXR1c19qb2ludCkKYGBgCgpgYGB7cn0KbGNfZGF0YSR2ZXJpZmljYXRpb25fc3RhdHVzIDwtIGFzLm51bWVyaWMoYXMuZmFjdG9yKGxjX2RhdGEkdmVyaWZpY2F0aW9uX3N0YXR1cykpCmxjX2RhdGEgPC0gbGNfZGF0YSAlPiUgc2VsZWN0KC12ZXJpZmljYXRpb25fc3RhdHVzX2pvaW50LCAtYXBwbGljYXRpb25fdHlwZSkKYGBgCgoKTGV0J3MgY2hlY2wgaWYgb3RoZXIgaXMgTkEgb3IgYSByZWFsIHZhbHVlIGZvciBwdXJwb3NlLiBJdCdzIGEgcmVhbCBvbmUsIHNvIHdlIGRvbid0IGhhdmUgdG8gaGFuZGxlIGl0LgpgYGB7cn0KbGNfZGF0YSRwdXJwb3NlIDwtIGFzLmZhY3RvcihsY19kYXRhJHB1cnBvc2UpCmdncGxvdChkYXRhID0gbGNfZGF0YSwgbWFwcGluZyA9IGFlcyh4PWludF9yYXRlLHk9cHVycG9zZSkpICsgZ2VvbV9ib3hwbG90KCkKbGNfZGF0YSRwdXJwb3NlIDwtIGFzLm51bWVyaWMobGNfZGF0YSRwdXJwb3NlKQpgYGAKTGV0J3MgaGF2ZSBhIGdsYW5jZSB0byB0aGUgc3RhdGUgYWRkcmVzczoKYGBge3J9CnRhYmxlKGxjX2RhdGEkYWRkcl9zdGF0ZSkKbGNfZGF0YSRhZGRyX3N0YXRlIDwtIGFzLmZhY3RvcihsY19kYXRhJGFkZHJfc3RhdGUpCmdncGxvdChkYXRhID0gbGNfZGF0YSwgbWFwcGluZyA9IGFlcyh4PWludF9yYXRlLHk9YWRkcl9zdGF0ZSkpICsgZ2VvbV9ib3hwbG90KCkKbGNfZGF0YSRhZGRyX3N0YXRlIDwtIGFzLm51bWVyaWMobGNfZGF0YSRhZGRyX3N0YXRlKQpgYGAKUmVnYXJkaW5nIGRlbGlucXVlbmN5IGluIHRoZSBsYXN0IDIgeWVhcnMsIHRoZXJlIGFyZSBmZXcgTkFzIHRoZW4gcmVtb3ZlIHRoZW06CmBgYHtyfQpsY19kYXRhIDwtIGxjX2RhdGEgJT4lIAogICAgZmlsdGVyKCEoaXMubmEoZGVsaW5xXzJ5cnMpKSkKYGBgCgoKYGBge3J9CmxjX2RhdGEgPC0gbGNfZGF0YSAlPiUKICBtdXRhdGUobXRoc19zaW5jZV9kZWxpbnFfY2F0ID0gaWZlbHNlKAogICAgaXMubmEobXRoc19zaW5jZV9sYXN0X2RlbGlucSkgPT0gVFJVRSwKICAgICJOT05FIiwKICAgIGlmZWxzZSgKICAgICAgbXRoc19zaW5jZV9sYXN0X2RlbGlucSA8PSAxMiwKICAgICAgIkxlc3NfMV9ZIiwKICAgICAgaWZlbHNlKAogICAgICAgIG10aHNfc2luY2VfbGFzdF9kZWxpbnEgPD0gMjQsCiAgICAgICAgIkxlc3NfMl9ZIiwKICAgICAgICBpZmVsc2UoCiAgICAgICAgICBtdGhzX3NpbmNlX2xhc3RfZGVsaW5xIDw9IDM2LAogICAgICAgICAgIkxlc3NfM19ZIiwKICAgICAgICAgIGlmZWxzZShtdGhzX3NpbmNlX2xhc3RfZGVsaW5xIDw9IDQ4LCAiTGVzc180X1kiLCAiTW9yZV80X1kiKQogICAgICAgICkKICAgICAgKQogICAgKQogICkpICU+JSBzZWxlY3QoLW10aHNfc2luY2VfbGFzdF9kZWxpbnEpCiAgICAgICAgICAKbGNfZGF0YSRtdGhzX3NpbmNlX2RlbGlucV9jYXQgPC0gYXMuZmFjdG9yKGxjX2RhdGEkbXRoc19zaW5jZV9kZWxpbnFfY2F0KQpnZ3Bsb3QoZGF0YSA9IGxjX2RhdGEsIG1hcHBpbmcgPSBhZXMoeD1pbnRfcmF0ZSx5PW10aHNfc2luY2VfZGVsaW5xX2NhdCkpK2dlb21fYm94cGxvdCgpCmxjX2RhdGEkbXRoc19zaW5jZV9kZWxpbnFfY2F0IDwtIGFzLm51bWVyaWMobGNfZGF0YSRtdGhzX3NpbmNlX2RlbGlucV9jYXQpCmBgYAoKYGBge3J9CmxjX2RhdGEgPC0gbGNfZGF0YSAlPiUKICBtdXRhdGUobXRoc19zaW5jZV9sYXN0X3JlY29yZF9jYXQgPSBpZmVsc2UoCiAgICBpcy5uYShtdGhzX3NpbmNlX2xhc3RfcmVjb3JkKSA9PSBUUlVFLAogICAgIk5PTkUiLAogICAgaWZlbHNlKAogICAgICBtdGhzX3NpbmNlX2xhc3RfcmVjb3JkIDw9IDEyLAogICAgICAiTGVzc18xX1kiLAogICAgICBpZmVsc2UoCiAgICAgICAgbXRoc19zaW5jZV9sYXN0X3JlY29yZCA8PSAyNCwKICAgICAgICAiTGVzc18yX1kiLAogICAgICAgIGlmZWxzZSgKICAgICAgICAgIG10aHNfc2luY2VfbGFzdF9yZWNvcmQgPD0gMzYsCiAgICAgICAgICAiTGVzc18zX1kiLAogICAgICAgICAgaWZlbHNlKG10aHNfc2luY2VfbGFzdF9yZWNvcmQgPD0gNDgsICJMZXNzXzRfWSIsICJNb3JlXzRfWSIpCiAgICAgICAgKQogICAgICApCiAgICApCiAgKSkgJT4lIHNlbGVjdCgtbXRoc19zaW5jZV9sYXN0X3JlY29yZCkKCmxjX2RhdGEkbXRoc19zaW5jZV9sYXN0X3JlY29yZF9jYXQgPC0gYXMuZmFjdG9yKGxjX2RhdGEkbXRoc19zaW5jZV9sYXN0X3JlY29yZF9jYXQpCmdncGxvdChkYXRhID0gbGNfZGF0YSwgbWFwcGluZyA9IGFlcyh4PWludF9yYXRlLHk9bXRoc19zaW5jZV9sYXN0X3JlY29yZF9jYXQpKStnZW9tX2JveHBsb3QoKQpsY19kYXRhJG10aHNfc2luY2VfbGFzdF9yZWNvcmRfY2F0IDwtIGFzLm51bWVyaWMobGNfZGF0YSRtdGhzX3NpbmNlX2xhc3RfcmVjb3JkX2NhdCkKYGBgCgpgYGB7cn0KbGNfZGF0YSA8LWxjX2RhdGEgJT4lIAogIG11dGF0ZShtdGhzX3NpbmNlX2xhc3RfbWFqb3JfZGVyb2dfY2F0ID0gIGlmZWxzZSgKICAgIGlzLm5hKG10aHNfc2luY2VfbGFzdF9tYWpvcl9kZXJvZykgPT0gVFJVRSwKICAgICJOT05FIiwKICAgIGlmZWxzZSgKICAgICAgbXRoc19zaW5jZV9sYXN0X21ham9yX2Rlcm9nIDw9IDEyLAogICAgICAiTGVzc18xX1kiLAogICAgICBpZmVsc2UoCiAgICAgICAgbXRoc19zaW5jZV9sYXN0X21ham9yX2Rlcm9nIDw9IDI0LAogICAgICAgICJMZXNzXzJfWSIsCiAgICAgICAgaWZlbHNlKAogICAgICAgICAgbXRoc19zaW5jZV9sYXN0X21ham9yX2Rlcm9nIDw9IDM2LAogICAgICAgICAgIkxlc3NfM19ZIiwKICAgICAgICAgIGlmZWxzZShtdGhzX3NpbmNlX2xhc3RfbWFqb3JfZGVyb2cgPD0gNDgsICJMZXNzXzRfWSIsICJNb3JlXzRfWSIpCiAgICAgICAgKQogICAgICApCiAgICApCiAgKSkgJT4lIHNlbGVjdCgtbXRoc19zaW5jZV9sYXN0X21ham9yX2Rlcm9nKQoKbGNfZGF0YSRtdGhzX3NpbmNlX2xhc3RfbWFqb3JfZGVyb2dfY2F0IDwtIGFzLmZhY3RvcihsY19kYXRhJG10aHNfc2luY2VfbGFzdF9tYWpvcl9kZXJvZ19jYXQpCmdncGxvdChkYXRhID0gbGNfZGF0YSwgbWFwcGluZyA9IGFlcyh4PWludF9yYXRlLHk9bXRoc19zaW5jZV9sYXN0X21ham9yX2Rlcm9nX2NhdCkpK2dlb21fYm94cGxvdCgpCmxjX2RhdGEkbXRoc19zaW5jZV9sYXN0X21ham9yX2Rlcm9nX2NhdCA8LSBhcy5udW1lcmljKGxjX2RhdGEkbXRoc19zaW5jZV9sYXN0X21ham9yX2Rlcm9nX2NhdCkKCmBgYAoKYGBge3J9CmxjX2RhdGEkaW5pdGlhbF9saXN0X3N0YXR1cyA8LSBhcy5mYWN0b3IobGNfZGF0YSRpbml0aWFsX2xpc3Rfc3RhdHVzKQpnZ3Bsb3QoZGF0YSA9IGxjX2RhdGEsIG1hcHBpbmcgPSBhZXMoeD1pbnRfcmF0ZSx5PWluaXRpYWxfbGlzdF9zdGF0dXMpKStnZW9tX2JveHBsb3QoKQpsY19kYXRhJGluaXRpYWxfbGlzdF9zdGF0dXMgPC0gYXMubnVtZXJpYyhsY19kYXRhJGluaXRpYWxfbGlzdF9zdGF0dXMpCmBgYAoKTGV0J3MgY2hlY2sgd2hpY2ggY29sdW1ucyBzdGlsbCBoYXZlIG51bGwgdmFsdWVzCmBgYHtyfQpjb2xTdW1zKGlzLm5hKGxjX2RhdGEpKQpgYGAKVGhlIGNvbHVtbnMgKipyZXZvbF9iYWwqKiBhbmQgKipyZXZvbF91dGlsKiogY29udGFpbiBvbmx5IGZldyBOQSB2YWx1ZXMsIHRob3NlIHZhbHVlcyBjYW4ndCBiZSByZXBsYWNlZCB3aXRoIDAsIHRoZW4gd2UgZmlsdGVyIHRoZSB2YWx1ZXMgd2hpY2ggYXJlIG5vdCBOQQpgYGB7cn0KbGNfZGF0YSA8LSBsY19kYXRhICU+JSAKICAgIGZpbHRlcighKGlzLm5hKHJldm9sX2JhbCkpKSAlPiUgCiAgICAgICAgZmlsdGVyKCEoaXMubmEocmV2b2xfdXRpbCkpKQpgYGAKCgpMZXQncyBjaGVjayB3aGljaCBjb2x1bW5zIHN0aWxsIGhhdmUgbnVsbCB2YWx1ZXMKYGBge3J9Cm5hbWVzKHdoaWNoKGNvbFN1bXMoaXMubmEobGNfZGF0YSkpID4gMCkpCmBgYAoKUmVwbGFjZSBudWxsIHZhbHVlcyB3aXRoIDAgd2hlcmUgaXMgcG9zc2libGUKYGBge3J9CmxjX2RhdGEgPC0KICBsY19kYXRhICU+JQogIG11dGF0ZShvcGVuX2FjY182bSA9IGlmZWxzZShpcy5uYShvcGVuX2FjY182bSkgPT0gVFJVRSwgMCwgb3Blbl9hY2NfNm0pKSAlPiUKICBtdXRhdGUodG90X2N1cl9iYWwgPSBpZmVsc2UoaXMubmEodG90X2N1cl9iYWwpID09IFRSVUUsIDAsIHRvdF9jdXJfYmFsKSkgJT4lCiAgbXV0YXRlKG9wZW5faWxfNm0gPSBpZmVsc2UoaXMubmEob3Blbl9pbF82bSkgPT0gVFJVRSwgMCwgb3Blbl9pbF82bSkpICU+JQogIG11dGF0ZShvcGVuX2lsXzEybSA9IGlmZWxzZShpcy5uYShvcGVuX2lsXzEybSkgPT0gVFJVRSwgMCwgb3Blbl9pbF8xMm0pKSAlPiUKICBtdXRhdGUob3Blbl9pbF8yNG0gPSBpZmVsc2UoaXMubmEob3Blbl9pbF8yNG0pID09IFRSVUUsIDAsIG9wZW5faWxfMjRtKSkgJT4lCiAgbXV0YXRlKG10aHNfc2luY2VfcmNudF9pbCA9IGlmZWxzZShpcy5uYShtdGhzX3NpbmNlX3JjbnRfaWwpID09IFRSVUUsIDAsIG10aHNfc2luY2VfcmNudF9pbCkpICU+JQogIG11dGF0ZSh0b3RhbF9iYWxfaWwgPSBpZmVsc2UoaXMubmEodG90YWxfYmFsX2lsKSA9PSBUUlVFLCAwLCB0b3RhbF9iYWxfaWwpKSAlPiUKICBtdXRhdGUoaWxfdXRpbCA9IGlmZWxzZShpcy5uYShpbF91dGlsKSA9PSBUUlVFLCAwLCBpbF91dGlsKSkgJT4lCiAgbXV0YXRlKG9wZW5fcnZfMTJtID0gaWZlbHNlKGlzLm5hKG9wZW5fcnZfMTJtKSA9PSBUUlVFLCAwLCBvcGVuX3J2XzEybSkpICU+JQogIG11dGF0ZSh0b3RhbF9yZXZfaGlfbGltID0gaWZlbHNlKGlzLm5hKHRvdGFsX3Jldl9oaV9saW0pID09IFRSVUUsIDAsIHRvdGFsX3Jldl9oaV9saW0pKSAlPiUKICBtdXRhdGUobWF4X2JhbF9iYyA9IGlmZWxzZShpcy5uYShtYXhfYmFsX2JjKSA9PSBUUlVFLCAwLCBtYXhfYmFsX2JjKSkgJT4lCiAgbXV0YXRlKGFsbF91dGlsID0gaWZlbHNlKGlzLm5hKGFsbF91dGlsKSA9PSBUUlVFLCAwLCBhbGxfdXRpbCkpICU+JQogIG11dGF0ZShpbnFfZmkgPSBpZmVsc2UoaXMubmEoaW5xX2ZpKSA9PSBUUlVFLCAwLCBpbnFfZmkpKSAlPiUKICBtdXRhdGUodG90YWxfY3VfdGwgPSBpZmVsc2UoaXMubmEodG90YWxfY3VfdGwpID09IFRSVUUsIDAsIHRvdGFsX2N1X3RsKSkgJT4lCiAgbXV0YXRlKGlucV9sYXN0XzEybSA9IGlmZWxzZShpcy5uYShpbnFfbGFzdF8xMm0pID09IFRSVUUsIDAsIGlucV9sYXN0XzEybSkpICU+JQogIG11dGF0ZShvcGVuX3J2XzI0bSA9IGlmZWxzZShpcy5uYShvcGVuX3J2XzI0bSkgPT0gVFJVRSwgMCwgb3Blbl9ydl8yNG0pKSAlPiUKICBtdXRhdGUodG90X2NvbGxfYW10ID0gaWZlbHNlKGlzLm5hKHRvdF9jb2xsX2FtdCk9PSBUUlVFLDAsIHRvdF9jb2xsX2FtdCkpICU+JQogIG11dGF0ZShjb2xsZWN0aW9uc18xMl9tdGhzX2V4X21lZCA9IGlmZWxzZShpcy5uYShjb2xsZWN0aW9uc18xMl9tdGhzX2V4X21lZCk9PSBUUlVFLDAsIGNvbGxlY3Rpb25zXzEyX210aHNfZXhfbWVkKSkKYGBgCgoqKmVhcmxpZXN0X2NyX2xpbmUqKiBjb250YWlucyB0aGUgbW9udGggdGhlIGJvcnJvd2VyJ3MgZWFybGllc3QgcmVwb3J0ZWQgY3JlZGl0IGxpbmUgd2FzIG9wZW5lZC4KRXZlbiBpZiB0aGlzIGRhdGUgY29uc2lzdHMgb25seSBvbiBtb250aCBhbmQgeWVhciwgc3RpbGwgdGhlcmUgYXJlIHRvbyBtYW55IHVuaXF1ZSB2YWx1ZXMuCldlIGNvdWxkIHRyYW5zZm9ybSB0aGUgZGF0ZXMgaW4gdG8gYSBudW1lcmljYWwgdmFsdWUsIGJ5IGNvbnZlcnRpbmcgdGhlbSBmcm9tIGRhdGUgaW50byBVbml4IFRpbWUuClRoaXMgdW5pdCBtZWFzdXJlcyB0aW1lIGJ5IHRoZSBudW1iZXIgb2Ygc2Vjb25kcyB0aGF0IGhhdmUgZWxhcHNlZCBzaW5jZSAwMDowMDowMCBVVEMgb24gMSBKYW51YXJ5IDE5NzAuClNpbmNlIHRoaXMgY29sdW1uIGRvZXNuJ3QgY29udGFpbiB0aGUgZGF5IG51bWJlciwgd2UgdGFrZSBhcyBhIHJlZmVyZW5jZSB0aGUgZmlyc3QgZGF5IG9mIHRoZSBtb250aC4KCmBgYHtyfQpsY19kYXRhIDwtIGxjX2RhdGEgJT4lIAogICAgZmlsdGVyKCEoaXMubmEoZWFybGllc3RfY3JfbGluZSkpKQoKIyBmdW5jdGlvbiB0byByZXBsYWNlIGRhdGVzIHdpdGggdW5peCB0aW1lCnRvX3VuaXhfdGltZSA8LSBmdW5jdGlvbihkYXRlKSB7CiAgdG1wIDwtIHBhc3RlKCIwMSIsIGRhdGUsIHNlcD0iLSIpCiAgcmV0dXJuIChhcy5udW1lcmljKGFzLlBPU0lYY3QodG1wLCBmb3JtYXQ9IiVkLSViLSVZIiwgdHo9IlVUQyIpKSkKfQoKIyBtYXAgZGF0ZXMgdG8gdW5peCB0aW1lCmxjX2RhdGEkZWFybGllc3RfY3JfbGluZSA8LSBhcHBseShsY19kYXRhLCAxLCBmdW5jdGlvbihyb3cpIHRvX3VuaXhfdGltZShyb3dbImVhcmxpZXN0X2NyX2xpbmUiXSkpCgojIHN0YW5kYXJkaXplIHRoZW0KI2xjX2RhdGEkZWFybGllc3RfY3JfbGluZSA8LSBzY2FsZShsY19kYXRhJGVhcmxpZXN0X2NyX2xpbmUpCmBgYAoKT3V0bGllcnMgUmVtb3ZhbC4KCmBgYHtyfQpib3hwbG90KGxjX2RhdGEkaW50X3JhdGUpCiMgSWRlbnRpZnkgb3V0bGllcnMgdXNpbmcgYm94cGxvdApvdXRsaWVycyA8LSBib3hwbG90KGxjX2RhdGEkaW50X3JhdGUsIHBsb3QgPSBGQUxTRSkkb3V0CiMgUmVtb3ZlIG91dGxpZXJzIGZyb20gdGhlIGRhdGFzZXQKbGNfZGF0YV9jbGVhbiA8LSBsY19kYXRhWyFsY19kYXRhJGludF9yYXRlICVpbiUgb3V0bGllcnMsIF0KYGBgCgpgYGB7cn0Kc3VtbWFyeShsY19kYXRhKQpgYGAKCmBgYHtyfQoKIyBUT0RPOiAocGFydGUgdmVjY2hpYSksIHNwbGl0IDgwLzIwIGUgbGluZWFyIHJlZ3Jlc3Npb24uLi4KIyBDcmVhdGUgaW5kaWNlcyBmb3Igc3BsaXR0aW5nICg4MCUgdHJhaW4sIDIwJSB0ZXN0KQp0cmFpbl9pbmRpY2VzIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24obGNfZGF0YSRpbnRfcmF0ZSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQoKIyBDcmVhdGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YXNldHMKdHJhaW5fZGF0YSA8LSBsY19kYXRhW3RyYWluX2luZGljZXMsIF0KdGVzdF9kYXRhIDwtIGxjX2RhdGFbLXRyYWluX2luZGljZXMsIF0KCiMjIyMgTGluZWFyIFJlZ3Jlc3Npb24gIyMjIwojbG0uZml0IDwtIGxtKGludF9yYXRlIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEpCgojIFRPRE86IGNoZWNrIGNvbGxpbmVhcml0eSBhbmQgbXVsdGljb2xsaW5lYXJpdHkKI3ZpZihsbS5maXQpICMgdGhlcmUgaXMgbXVsdGljb2xsaW5lYXJpdHkKI2NvcihsY19kYXRhKSAKCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhCiN0cmFpbl9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGxtLmZpdCwgbmV3ZGF0YSA9IHRyYWluX2RhdGEpCiN0ZXN0X3ByZWRpY3Rpb25zIDwtIHByZWRpY3QobG0uZml0LCBuZXdkYXRhID0gdGVzdF9kYXRhKQoKIyBFdmFsdWF0ZSBtb2RlbCBwZXJmb3JtYW5jZSBvbiB0cmFpbmluZyBkYXRhCiN0cmFpbl9ybXNlIDwtIHNxcnQobWVhbigodHJhaW5fcHJlZGljdGlvbnMgLSB0cmFpbl9kYXRhJGludF9yYXRlKV4yKSkKI3RyYWluX3Jfc3F1YXJlZCA8LSBzdW1tYXJ5KGxtLmZpdCkkci5zcXVhcmVkCgojIEV2YWx1YXRlIG1vZGVsIHBlcmZvcm1hbmNlIG9uIHRlc3RpbmcgZGF0YQojdGVzdF9ybXNlIDwtIHNxcnQobWVhbigodGVzdF9wcmVkaWN0aW9ucyAtIHRlc3RfZGF0YSRpbnRfcmF0ZSleMikpCiN0ZXN0X3Jfc3F1YXJlZCA8LSBzdW1tYXJ5KGxtLmZpdCwgdGVzdF9kYXRhKSRyLnNxdWFyZWQKCiMgUHJpbnQgZXZhbHVhdGlvbiBtZXRyaWNzCiNjYXQoIlRyYWluaW5nIFJNU0U6IiwgdHJhaW5fcm1zZSwgIlxuIikKI2NhdCgiVHJhaW5pbmcgUi1zcXVhcmVkOiIsIHRyYWluX3Jfc3F1YXJlZCwgIlxuIikKI3Jtc2UgPC0gc3FydChtZWFuKGxtLmZpdCRyZXNpZHVhbHNeMikpCiNwcmludChybXNlKQpgYGAKCmBgYHtyfQojIyMjIExpbmVhciBSZWdyZXNzaW9uICMjIyMKCmxtLmZpdCA8LSBsbShpbnRfcmF0ZSB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhKQoKIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhCmxtLnRyYWluX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QobG0uZml0LCBuZXdkYXRhID0gdHJhaW5fZGF0YSkKbG0udGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGxtLmZpdCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkKCiMgQ2FsY3VsYXRlIE1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKbG0udHJhaW5fbXNlIDwtIG1lYW4oKGxtLnRyYWluX3ByZWRpY3Rpb25zIC0gdHJhaW5fZGF0YSRpbnRfcmF0ZSleMikKbG0udGVzdF9tc2UgPC0gbWVhbigobG0udGVzdF9wcmVkaWN0aW9ucyAtIHRlc3RfZGF0YSRpbnRfcmF0ZSleMikKCiMgQ2FsY3VsYXRlIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKbG0udHJhaW5fcm1zZSA8LSBzcXJ0KGxtLnRyYWluX21zZSkKbG0udGVzdF9ybXNlIDwtIHNxcnQobG0udGVzdF9tc2UpCgojIENhbGN1bGF0ZSBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwpsbS50cmFpbl9tYWUgPC0gbWVhbihhYnMobG0udHJhaW5fcHJlZGljdGlvbnMgLSB0cmFpbl9kYXRhJGludF9yYXRlKSkKbG0udGVzdF9tYWUgPC0gbWVhbihhYnMobG0udGVzdF9wcmVkaWN0aW9ucyAtIHRlc3RfZGF0YSRpbnRfcmF0ZSkpCgojIENhbGN1bGF0ZSBSLXNxdWFyZWQgKFLCsikgZm9yIHRyYWluaW5nIGFuZCB0ZXN0aW5nCmxtLnRyYWluX3IyIDwtIDEgLSAoc3VtKCh0cmFpbl9kYXRhJGludF9yYXRlIC0gbG0udHJhaW5fcHJlZGljdGlvbnMpXjIpIC8gc3VtKCh0cmFpbl9kYXRhJGludF9yYXRlIC0gbWVhbih0cmFpbl9kYXRhJGludF9yYXRlKSleMikpCmxtLnRlc3RfcjIgPC0gMSAtIChzdW0oKHRlc3RfZGF0YSRpbnRfcmF0ZSAtIGxtLnRlc3RfcHJlZGljdGlvbnMpXjIpIC8gc3VtKCh0ZXN0X2RhdGEkaW50X3JhdGUgLSBtZWFuKHRlc3RfZGF0YSRpbnRfcmF0ZSkpXjIpKQoKIyBEaXNwbGF5IHRoZSBtZXRyaWNzCmNhdCgiVHJhaW5pbmcgTVNFOiIsIGxtLnRyYWluX21zZSwgIlxuIikKY2F0KCJUZXN0aW5nIE1TRToiLCBsbS50ZXN0X21zZSwgIlxuIikKY2F0KCJUcmFpbmluZyBSTVNFOiIsIGxtLnRyYWluX3Jtc2UsICJcbiIpCmNhdCgiVGVzdGluZyBSTVNFOiIsIGxtLnRlc3Rfcm1zZSwgIlxuIikKY2F0KCJUcmFpbmluZyBNQUU6IiwgbG0udHJhaW5fbWFlLCAiXG4iKQpjYXQoIlRlc3RpbmcgTUFFOiIsIGxtLnRlc3RfbWFlLCAiXG4iKQpjYXQoIlRyYWluaW5nIFItc3F1YXJlZCAoUsKyKToiLCBsbS50cmFpbl9yMiwgIlxuIikKY2F0KCJUZXN0aW5nIFItc3F1YXJlZCAoUsKyKToiLCBsbS50ZXN0X3IyLCAiXG4iKQpgYGAKTGFzc28KSXQgc3RhbmRhcmRpemVzIGRhdGEgYXV0b21hdGljYWxseQoKYGBge3J9Cmxhc3NvLnByZWRpY3RvcnNfdHJhaW4gPC0gbW9kZWwubWF0cml4KGludF9yYXRlIH4gLiwgdHJhaW5fZGF0YSlbLC0xXQpsYXNzby50YXJnZXRfdHJhaW4gPC0gdHJhaW5fZGF0YSRpbnRfcmF0ZQpsYXNzby5wcmVkaWN0b3JzX3Rlc3QgPC0gbW9kZWwubWF0cml4KGludF9yYXRlIH4gLiwgdGVzdF9kYXRhKVssLTFdCmxhc3NvLnRhcmdldF90ZXN0IDwtIHRlc3RfZGF0YSRpbnRfcmF0ZQoKbGFzc28uZml0IDwtIGdsbW5ldChsYXNzby5wcmVkaWN0b3JzX3RyYWluLCBsYXNzby50YXJnZXRfdHJhaW4sIGFscGhhID0gMSkKCmRpbShjb2VmKGxhc3NvLmZpdCkpCiMgV2UgaGF2ZSBvbmx5IDY5IHJvd3MsIGJlY2F1c2UgZ2xtbmV0IGhhcyBhIHN0b3AgY3JpdGVyaW9uLCBzZWUgaGVscC4KCmxhc3NvLmZpdAojIFdlIG9ic2VydmUgdGhhdCBzb21lIG9mIHRoZSBjb2VmZmljaWVudHMgYXJlIHNldCB0byB6ZXJvIChEZj0wLCAlRGV2PTApCgpwbG90KGxhc3NvLmZpdCwgbGFiZWw9VFJVRSkKIyBBbHNvIGhlcmUgd2UgY2FuIHNlZSB0aGF0IHNvbWUgb2YgdGhlIGNvZWZmaWNpZW50cyBhcmUgc2V0IHRvIHplcm8uCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YQoKbGFzc28udHJhaW5fcHJlZGljdGlvbnMgPC0gcHJlZGljdChsYXNzby5maXQsIG5ld2RhdGEgPSB0cmFpbl9kYXRhLCBuZXd4ID0gbGFzc28ucHJlZGljdG9yc190cmFpbikKbGFzc28udGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGxhc3NvLmZpdCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSwgbmV3eCA9IGxhc3NvLnByZWRpY3RvcnNfdHJhaW4pCgojIENhbGN1bGF0ZSBNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSkgZm9yIHRyYWluaW5nIGFuZCB0ZXN0aW5nCmxhc3NvLnRyYWluX21zZSA8LSBtZWFuKChsYXNzby50cmFpbl9wcmVkaWN0aW9ucyAtIHRyYWluX2RhdGEkaW50X3JhdGUpXjIpCmxhc3NvLnRlc3RfbXNlIDwtIG1lYW4oKGxhc3NvLnRlc3RfcHJlZGljdGlvbnMgLSB0ZXN0X2RhdGEkaW50X3JhdGUpXjIpCgojIENhbGN1bGF0ZSBSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSkgZm9yIHRyYWluaW5nIGFuZCB0ZXN0aW5nCmxhc3NvLnRyYWluX3Jtc2UgPC0gc3FydChsYXNzby50cmFpbl9tc2UpCmxhc3NvLnRlc3Rfcm1zZSA8LSBzcXJ0KGxhc3NvLnRlc3RfbXNlKQoKIyBDYWxjdWxhdGUgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKbGFzc28udHJhaW5fbWFlIDwtIG1lYW4oYWJzKGxhc3NvLnRyYWluX3ByZWRpY3Rpb25zIC0gdHJhaW5fZGF0YSRpbnRfcmF0ZSkpCmxhc3NvLnRlc3RfbWFlIDwtIG1lYW4oYWJzKGxhc3NvLnRlc3RfcHJlZGljdGlvbnMgLSB0ZXN0X2RhdGEkaW50X3JhdGUpKQoKIyBDYWxjdWxhdGUgUi1zcXVhcmVkIChSwrIpIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwpsYXNzby50cmFpbl9yMiA8LSAxIC0gKHN1bSgodHJhaW5fZGF0YSRpbnRfcmF0ZSAtIGxhc3NvLnRyYWluX3ByZWRpY3Rpb25zKV4yKSAvIHN1bSgodHJhaW5fZGF0YSRpbnRfcmF0ZSAtIG1lYW4odHJhaW5fZGF0YSRpbnRfcmF0ZSkpXjIpKQpsYXNzby50ZXN0X3IyIDwtIDEgLSAoc3VtKCh0ZXN0X2RhdGEkaW50X3JhdGUgLSBsYXNzby50ZXN0X3ByZWRpY3Rpb25zKV4yKSAvIHN1bSgodGVzdF9kYXRhJGludF9yYXRlIC0gbWVhbih0ZXN0X2RhdGEkaW50X3JhdGUpKV4yKSkKCiMgRGlzcGxheSB0aGUgbWV0cmljcwpjYXQoIlRyYWluaW5nIE1TRToiLCBsYXNzby50cmFpbl9tc2UsICJcbiIpCmNhdCgiVGVzdGluZyBNU0U6IiwgbGFzc28udGVzdF9tc2UsICJcbiIpCmNhdCgiVHJhaW5pbmcgUk1TRToiLCBsYXNzby50cmFpbl9ybXNlLCAiXG4iKQpjYXQoIlRlc3RpbmcgUk1TRToiLCBsYXNzby50ZXN0X3Jtc2UsICJcbiIpCmNhdCgiVHJhaW5pbmcgTUFFOiIsIGxhc3NvLnRyYWluX21hZSwgIlxuIikKY2F0KCJUZXN0aW5nIE1BRToiLCBsYXNzby50ZXN0X21hZSwgIlxuIikKY2F0KCJUcmFpbmluZyBSLXNxdWFyZWQgKFLCsik6IiwgbGFzc28udHJhaW5fcjIsICJcbiIpCmNhdCgiVGVzdGluZyBSLXNxdWFyZWQgKFLCsik6IiwgbGFzc28udGVzdF9yMiwgIlxuIikKYGBgCgpLIGZvbGQgdXNpbmcgSz01OgpgYGB7cn0KIyBEZWZpbmUgdGhlIG51bWJlciBvZiBmb2xkcyBmb3IgY3Jvc3MtdmFsaWRhdGlvbgpudW1fZm9sZHMgPC0gNQpmb2xkcyA8LSBjcmVhdGVGb2xkcyh0cmFpbl9kYXRhJGludF9yYXRlLCBrID0gbnVtX2ZvbGRzLCBsaXN0ID0gVFJVRSkKYGBgCgoKSyBmb2xkIHVzaW5nIEs9NSBhbmQgbGluZWFyIHJlZ3Jlc3Npb246CmBgYHtyfQojIyMjIExpbmVhciBSZWdyZXNpb24gYXBwbHlpbmcgQ3Jvc3MgVmFsaWRhdGlvbiB3aXRoIGs9NSAgIyMjIwoKIyBJbml0aWFsaXplIGxpc3RzIHRvIHN0b3JlIG1vZGVscyBhbmQgdGhlaXIgcmVzdWx0cwpsbS5rNS5tb2RlbHMgPC0gbGlzdCgpCmxtLms1LnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgpCgojIFBlcmZvcm0gay1mb2xkIGNyb3NzLXZhbGlkYXRpb24KZm9yKGkgaW4gc2VxX2Fsb25nKGZvbGRzKSkgewogICMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBmb3IgdGhlIGN1cnJlbnQgZm9sZAogIHRyYWluX2luZGljZXMgPC0gZm9sZHNbW2ldXQogIHRlc3RfaW5kaWNlcyA8LSBzZXRkaWZmKHNlcV9sZW4obnJvdyh0cmFpbl9kYXRhKSksIHRyYWluX2luZGljZXMpCiAgCiAgdHJhaW5fZGF0YV9mb2xkIDwtIHRyYWluX2RhdGFbdHJhaW5faW5kaWNlcywgXQogIHRlc3RfZGF0YV9mb2xkIDwtIHRyYWluX2RhdGFbdGVzdF9pbmRpY2VzLCBdCiAgCiAgIyBGaXQgdGhlIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBmb2xkCiAgbG0uazUgPC0gbG0oaW50X3JhdGUgfiAuLCBkYXRhID0gdHJhaW5fZGF0YV9mb2xkKQogIGxtLms1Lm1vZGVsc1tbaV1dIDwtIGxtLms1ICAjIFN0b3JlIHRoZSBtb2RlbAogIAogICMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZm9sZAogIGxtLms1LnRyYWluX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QobG0uazUsIG5ld2RhdGEgPSB0cmFpbl9kYXRhX2ZvbGQpCiAgbG0uazUudGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGxtLms1LCBuZXdkYXRhID0gdGVzdF9kYXRhX2ZvbGQpCiAgCiAgIyBDYWxjdWxhdGUgbWV0cmljcyBmb3IgdHJhaW5pbmcgZm9sZAogIGxtLms1LnRyYWluX21zZSA8LSBtZWFuKChsbS5rNS50cmFpbl9wcmVkaWN0aW9ucyAtIHRyYWluX2RhdGFfZm9sZCRpbnRfcmF0ZSleMikKICBsbS5rNS50cmFpbl9ybXNlIDwtIHNxcnQobG0uazUudHJhaW5fbXNlKQogIGxtLms1LnRyYWluX21hZSA8LSBtZWFuKGFicyhsbS5rNS50cmFpbl9wcmVkaWN0aW9ucyAtIHRyYWluX2RhdGFfZm9sZCRpbnRfcmF0ZSkpCiAgbG0uazUudHJhaW5fcjIgPC0gc3VtbWFyeShsbS5rNSkkci5zcXVhcmVkCiAgCiAgIyBDYWxjdWxhdGUgbWV0cmljcyBmb3IgdGVzdGluZyBmb2xkCiAgbG0uazUudGVzdF9tc2UgPC0gbWVhbigobG0uazUudGVzdF9wcmVkaWN0aW9ucyAtIHRlc3RfZGF0YV9mb2xkJGludF9yYXRlKV4yKQogIGxtLms1LnRlc3Rfcm1zZSA8LSBzcXJ0KGxtLms1LnRlc3RfbXNlKQogIGxtLms1LnRlc3RfbWFlIDwtIG1lYW4oYWJzKGxtLms1LnRlc3RfcHJlZGljdGlvbnMgLSB0ZXN0X2RhdGFfZm9sZCRpbnRfcmF0ZSkpCiAgbG0uazUudGVzdF9yMiA8LSAxIC0gKHN1bSgodGVzdF9kYXRhX2ZvbGQkaW50X3JhdGUgLSBsbS5rNS50ZXN0X3ByZWRpY3Rpb25zKV4yKSAvIHN1bSgodGVzdF9kYXRhX2ZvbGQkaW50X3JhdGUgLSBtZWFuKHRlc3RfZGF0YV9mb2xkJGludF9yYXRlKSleMikpCiAgCiAgIyBTdG9yZSBtZXRyaWNzIGluIHRoZSByZXN1bHRzIGRhdGFmcmFtZQogIGxtLms1LnJlc3VsdHMgPC0gcmJpbmQobG0uazUucmVzdWx0cywgZGF0YS5mcmFtZSgKICAgIEZvbGQgPSBpLAogICAgVHJhaW5fTVNFID0gbG0uazUudHJhaW5fbXNlLCBUZXN0X01TRSA9IGxtLms1LnRlc3RfbXNlLAogICAgVHJhaW5fUk1TRSA9IGxtLms1LnRyYWluX3Jtc2UsIFRlc3RfUk1TRSA9IGxtLms1LnRlc3Rfcm1zZSwKICAgIFRyYWluX01BRSA9IGxtLms1LnRyYWluX21hZSwgVGVzdF9NQUUgPSBsbS5rNS50ZXN0X21hZSwKICAgIFRyYWluX1IyID0gbG0uazUudHJhaW5fcjIsIFRlc3RfUjIgPSBsbS5rNS50ZXN0X3IyCiAgKSkKfQoKIyBEaXNwbGF5IHRoZSBtb2RlbHMgYW5kIHRoZWlyIG1ldHJpY3MKcHJpbnQobG0uazUubW9kZWxzKQpwcmludChsbS5rNS5yZXN1bHRzKQpgYGAKCmBgYHtyfQpsb25nX2RhdGEgPC0gcGl2b3RfbG9uZ2VyKGxtLms1LnJlc3VsdHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSAtRm9sZCwgICMgRXhjbHVkZSBGb2xkIGZyb20gcmVzaGFwaW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiTWV0cmljIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gIlZhbHVlIikKCiMgQ2hlY2sgdGhlIHJlc2hhcGVkIGRhdGEKcHJpbnQoaGVhZChsb25nX2RhdGEpKQpgYGAKCmBgYHtyfQoKIyBFbnN1cmUgdGhhdCB0aGUgJ0ZvbGQnIGNvbHVtbiBpbiB0aGUgcmVzaGFwZWQgZGF0YSBjb250YWlucyBhbGwgZm9sZCBudW1iZXJzCnByaW50KHVuaXF1ZShsb25nX2RhdGEkRm9sZCkpCiMgUGxvdCBmb3IgVHJhaW5pbmcgTWV0cmljcwpnZ3Bsb3QobG9uZ19kYXRhX3RyYWluaW5nLCBhZXMoeCA9IGZhY3RvcihGb2xkKSwgeSA9IFZhbHVlLCBjb2xvciA9IE1ldHJpYykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsgICMgVXNlIHRoaXMgb25seSBpZiBpdCBtYWtlcyBzZW5zZSB0byBjb25uZWN0IHBvaW50cyBhY3Jvc3MgZm9sZHMKICBsYWJzKHRpdGxlID0gIlRyYWluaW5nIE1ldHJpY3MgcGVyIEZvbGQgLSBMaW5lYXIgUmVncmVzc2lvbiIsCiAgICAgICB4ID0gIkZvbGQiLAogICAgICAgeSA9ICJNZXRyaWMgVmFsdWUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lID0gIlRyYWluaW5nIE1ldHJpY3MiKQoKZ2dwbG90KHN1YnNldChsb25nX2RhdGEsIGdyZXBsKCJUcmFpbiIsIE1ldHJpYykpLCBhZXMoeCA9IGZhY3RvcihGb2xkKSwgeSA9IFZhbHVlLCBjb2xvciA9IE1ldHJpYykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsgICMgVXNlIHRoaXMgb25seSBpZiBpdCBtYWtlcyBzZW5zZSB0byBjb25uZWN0IHBvaW50cyBhY3Jvc3MgZm9sZHMKICBsYWJzKHRpdGxlID0gIlRyYWluaW5nIE1ldHJpY3MgcGVyIEZvbGQgLSBMaW5lYXIgUmVncmVzc2lvbiIsCiAgICAgICB4ID0gIkZvbGQiLAogICAgICAgeSA9ICJNZXRyaWMgVmFsdWUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lID0gIlRyYWluaW5nIE1ldHJpY3MiKQojIFBsb3QgZm9yIFRlc3RpbmcgTWV0cmljcwoKZ2dwbG90KGxvbmdfZGF0YV90ZXN0aW5nLCBhZXMoeCA9IGZhY3RvcihGb2xkKSwgeSA9IFZhbHVlLCBjb2xvciA9IE1ldHJpYykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsgICMgVXNlIHRoaXMgb25seSBpZiBpdCBtYWtlcyBzZW5zZSB0byBjb25uZWN0IHBvaW50cyBhY3Jvc3MgZm9sZHMKICBsYWJzKHRpdGxlID0gIlRlc3RpbmcgTWV0cmljcyBwZXIgRm9sZCAtIExpbmVhciBSZWdyZXNzaW9uIiwKICAgICAgIHggPSAiRm9sZCIsCiAgICAgICB5ID0gIk1ldHJpYyBWYWx1ZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWUgPSAiVGVzdGluZyBNZXRyaWNzIikKYGBgCgoKSyBmb2xkIHVzaW5nIEs9NSBhbmQgUmFuZG9tIEZvcmVzdDoKYGBge3J9CiMjIyMgUmFuZG9tIEZvcmVzdCBhcHBseWluZyBDcm9zcyBWYWxpZGF0aW9uIHdpdGggaz01ICAjIyMjCgojIEluaXRpYWxpemUgbGlzdHMgdG8gc3RvcmUgbW9kZWxzIGFuZCB0aGVpciByZXN1bHRzCnJmLms1Lm1vZGVscyA8LSBsaXN0KCkKcmYuazUucmVzdWx0cyA8LSBkYXRhLmZyYW1lKCkKCiMgUGVyZm9ybSBrLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbgpmb3IoaSBpbiBzZXFfYWxvbmcoZm9sZHMpKSB7CiAgIyBTcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGZvciB0aGUgY3VycmVudCBmb2xkCiAgdHJhaW5faW5kaWNlcyA8LSBmb2xkc1tbaV1dCiAgdGVzdF9pbmRpY2VzIDwtIHNldGRpZmYoc2VxX2xlbihucm93KHRyYWluX2RhdGEpKSwgdHJhaW5faW5kaWNlcykKICAKICB0cmFpbl9kYXRhX2ZvbGQgPC0gdHJhaW5fZGF0YVt0cmFpbl9pbmRpY2VzLCBdCiAgdGVzdF9kYXRhX2ZvbGQgPC0gdHJhaW5fZGF0YVt0ZXN0X2luZGljZXMsIF0KICAKICAjIEZpdCB0aGUgbW9kZWwgb24gdGhlIHRyYWluaW5nIGZvbGQKICByZi5rNSA8LSByYW5nZXIoZm9ybXVsYSA9IGludF9yYXRlIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEsIG51bS50cmVlcyA9IDUwMCwgdmVyYm9zZT1UUlVFLCBpbXBvcnRhbmNlID0gImltcHVyaXR5Iiwgb29iLmVycm9yID0gVFJVRSkKICByZi5rNS5tb2RlbHNbW2ldXSA8LSByZi5rNSAgIyBTdG9yZSB0aGUgbW9kZWwKICAKICAjIE1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGZvbGQKICByZi5rNS50cmFpbl9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJmLms1LCBkYXRhID0gdHJhaW5fZGF0YV9mb2xkKQogIHJmLms1LnRlc3RfcHJlZGljdGlvbnMgPC0gcHJlZGljdChyZi5rNSwgZGF0YSA9IHRlc3RfZGF0YV9mb2xkKQogIAogICMgQ2FsY3VsYXRlIG1ldHJpY3MgZm9yIHRyYWluaW5nIGZvbGQKICByZi5rNS50cmFpbl9tc2UgPC0gbWVhbigocmYuazUudHJhaW5fcHJlZGljdGlvbnMgLSB0cmFpbl9kYXRhX2ZvbGQkaW50X3JhdGUpXjIpCiAgcmYuazUudHJhaW5fcm1zZSA8LSBzcXJ0KHJmLms1LnRyYWluX21zZSkKICByZi5rNS50cmFpbl9tYWUgPC0gbWVhbihhYnMocmYuazUudHJhaW5fcHJlZGljdGlvbnMgLSB0cmFpbl9kYXRhX2ZvbGQkaW50X3JhdGUpKQogIHJmLms1LnRyYWluX3IyIDwtIHN1bW1hcnkocmYuazUpJHIuc3F1YXJlZAogIAogICMgQ2FsY3VsYXRlIG1ldHJpY3MgZm9yIHRlc3RpbmcgZm9sZAogIHJmLms1LnRlc3RfbXNlIDwtIG1lYW4oKHJmLms1LnRlc3RfcHJlZGljdGlvbnMgLSB0ZXN0X2RhdGFfZm9sZCRpbnRfcmF0ZSleMikKICByZi5rNS50ZXN0X3Jtc2UgPC0gc3FydChyZi5rNS50ZXN0X21zZSkKICByZi5rNS50ZXN0X21hZSA8LSBtZWFuKGFicyhyZi5rNS50ZXN0X3ByZWRpY3Rpb25zIC0gdGVzdF9kYXRhX2ZvbGQkaW50X3JhdGUpKQogIHJmLms1LnRlc3RfcjIgPC0gMSAtIChzdW0oKHRlc3RfZGF0YV9mb2xkJGludF9yYXRlIC0gcmYuazUudGVzdF9wcmVkaWN0aW9ucyleMikgLyBzdW0oKHRlc3RfZGF0YV9mb2xkJGludF9yYXRlIC0gbWVhbih0ZXN0X2RhdGFfZm9sZCRpbnRfcmF0ZSkpXjIpKQogIAogICMgU3RvcmUgbWV0cmljcyBpbiB0aGUgcmVzdWx0cyBkYXRhZnJhbWUKICByZi5rNS5yZXN1bHRzIDwtIHJiaW5kKHJmLms1LnJlc3VsdHMsIGRhdGEuZnJhbWUoCiAgICBGb2xkID0gaSwKICAgIFRyYWluX01TRSA9IHJmLms1LnRyYWluX21zZSwgVGVzdF9NU0UgPSByZi5rNS50ZXN0X21zZSwKICAgIFRyYWluX1JNU0UgPSByZi5rNS50cmFpbl9ybXNlLCBUZXN0X1JNU0UgPSByZi5rNS50ZXN0X3Jtc2UsCiAgICBUcmFpbl9NQUUgPSByZi5rNS50cmFpbl9tYWUsIFRlc3RfTUFFID0gcmYuazUudGVzdF9tYWUsCiAgICBUcmFpbl9SMiA9IHJmLms1LnRyYWluX3IyLCBUZXN0X1IyID0gcmYuazUudGVzdF9yMgogICkpCn0KCiMgRGlzcGxheSB0aGUgbW9kZWxzIGFuZCB0aGVpciBtZXRyaWNzCnByaW50KHJmLms1Lm1vZGVscykKcHJpbnQocmYuazUucmVzdWx0cykKYGBgCgoKSyBmb2xkIHVzaW5nIEs9NSBhbmQgQm9vc3Rpbmc6CmBgYHtyfQojIyMjIEJvb3N0aW5nIGFwcGx5aW5nIENyb3NzIFZhbGlkYXRpb24gd2l0aCBrPTUgICMjIyMKCiMgSW5pdGlhbGl6ZSBsaXN0cyB0byBzdG9yZSBtb2RlbHMgYW5kIHRoZWlyIHJlc3VsdHMKeGdiLms1Lm1vZGVscyA8LSBsaXN0KCkKeGdiLms1LnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgpCgojIFBlcmZvcm0gay1mb2xkIGNyb3NzLXZhbGlkYXRpb24KZm9yKGkgaW4gc2VxX2Fsb25nKGZvbGRzKSkgewogICMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBmb3IgdGhlIGN1cnJlbnQgZm9sZAogIHRyYWluX2luZGljZXMgPC0gZm9sZHNbW2ldXQogIHRlc3RfaW5kaWNlcyA8LSBzZXRkaWZmKHNlcV9sZW4obnJvdyh0cmFpbl9kYXRhKSksIHRyYWluX2luZGljZXMpCiAgCiAgdHJhaW5fZGF0YV9mb2xkIDwtIHRyYWluX2RhdGFbdHJhaW5faW5kaWNlcywgXQogIHRlc3RfZGF0YV9mb2xkIDwtIHRyYWluX2RhdGFbdGVzdF9pbmRpY2VzLCBdCiAgCiAgIyBQcmVwYXJlIGRhdGEgZm9yIHhnYm9vc3QKICB4Z2IueV90cmFpbl9mb2xkIDwtIHRyYWluX2RhdGFfZm9sZCRpbnRfcmF0ZQogIHhnYi5YX3RyYWluX2ZvbGQgPC0gYXMubWF0cml4KHRyYWluX2RhdGFfZm9sZFssIC13aGljaChuYW1lcyh0cmFpbl9kYXRhX2ZvbGQpID09ICdpbnRfcmF0ZScpXSkKICAKICB4Z2IueV90ZXN0X2ZvbGQgPC0gdGVzdF9kYXRhX2ZvbGQkaW50X3JhdGUKICB4Z2IuWF90ZXN0X2ZvbGQgPC0gYXMubWF0cml4KHRlc3RfZGF0YV9mb2xkWywgLXdoaWNoKG5hbWVzKHRlc3RfZGF0YV9mb2xkKSA9PSAnaW50X3JhdGUnKV0pCiAgCiAgIyBGaXQgdGhlIHhnYm9vc3QgbW9kZWwgb24gdGhlIHRyYWluaW5nIGZvbGQKICB4Z2IuazUgPC0geGdib29zdCgKICAgIGRhdGEgPSB4Z2IuWF90cmFpbl9mb2xkLAogICAgbGFiZWwgPSB4Z2IueV90cmFpbl9mb2xkLAogICAgbnJvdW5kcyA9IDEwMCwKICAgIHZlcmJvc2UgPSAwCiAgKQogIHhnYi5rNS5tb2RlbHNbW2ldXSA8LSB4Z2IuazUgICMgU3RvcmUgdGhlIG1vZGVsCiAgCiAgIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0cmFpbmluZyBmb2xkCiAgeGdiLms1LnRyYWluX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QoeGdiLms1LCBuZXdkYXRhID0geGdiLlhfdHJhaW5fZm9sZCkKICAjIE1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3RpbmcgZm9sZAogIHhnYi5rNS50ZXN0X3ByZWRpY3Rpb25zIDwtIHByZWRpY3QoeGdiLms1LCBuZXdkYXRhID0geGdiLlhfdGVzdF9mb2xkKQogIAogICMgQ2FsY3VsYXRlIG1ldHJpY3MgZm9yIHRyYWluaW5nIGZvbGQKICB4Z2IuazUudHJhaW5fbXNlIDwtIG1lYW4oKHhnYi5rNS50cmFpbl9wcmVkaWN0aW9ucyAtIHRyYWluX2RhdGFfZm9sZCRpbnRfcmF0ZSleMikKICB4Z2IuazUudHJhaW5fcm1zZSA8LSBzcXJ0KHhnYi5rNS50cmFpbl9tc2UpCiAgeGdiLms1LnRyYWluX21hZSA8LSBtZWFuKGFicyh4Z2IuazUudHJhaW5fcHJlZGljdGlvbnMgLSB0cmFpbl9kYXRhX2ZvbGQkaW50X3JhdGUpKQogIHhnYi5rNS50cmFpbl9yMiA8LSAxIC0gKHN1bSgoeGdiLnlfdHJhaW5fZm9sZCAtIHhnYi5rNS50cmFpbl9wcmVkaWN0aW9ucyleMikgLyBzdW0oKHhnYi55X3RyYWluX2ZvbGQgLSBtZWFuKHhnYi55X3RyYWluX2ZvbGQpKV4yKSkKCiAgIyBDYWxjdWxhdGUgbWV0cmljcyBmb3IgdGVzdGluZyBmb2xkCiAgeGdiLms1LnRlc3RfbXNlIDwtIG1lYW4oKHhnYi5rNS50ZXN0X3ByZWRpY3Rpb25zIC0geGdiLnlfdGVzdF9mb2xkKV4yKQogIHhnYi5rNS50ZXN0X3Jtc2UgPC0gc3FydCh4Z2IuazUudGVzdF9tc2UpCiAgeGdiLms1LnRlc3RfbWFlIDwtIG1lYW4oYWJzKHhnYi5rNS50ZXN0X3ByZWRpY3Rpb25zIC0geGdiLnlfdGVzdF9mb2xkKSkKICB4Z2IuazUudGVzdF9yMiA8LSAxIC0gKHN1bSgoeGdiLnlfdGVzdF9mb2xkIC0geGdiLms1LnRlc3RfcHJlZGljdGlvbnMpXjIpIC8gc3VtKCh4Z2IueV90ZXN0X2ZvbGQgLSBtZWFuKHhnYi55X3Rlc3RfZm9sZCkpXjIpKSAgCiAgCiAgIyBTdG9yZSBtZXRyaWNzIGluIHRoZSByZXN1bHRzIGRhdGFmcmFtZQogIHhnYi5rNS5yZXN1bHRzIDwtIHJiaW5kKHhnYi5rNS5yZXN1bHRzLCBkYXRhLmZyYW1lKAogICAgRm9sZCA9IGksCiAgICBUcmFpbl9NU0UgPSB4Z2IuazUudHJhaW5fbXNlLCBUZXN0X01TRSA9IHhnYi5rNS50ZXN0X21zZSwKICAgIFRyYWluX1JNU0UgPSB4Z2IuazUudHJhaW5fcm1zZSwgVGVzdF9STVNFID0geGdiLms1LnRlc3Rfcm1zZSwKICAgIFRyYWluX01BRSA9IHhnYi5rNS50cmFpbl9tYWUsIFRlc3RfTUFFID0geGdiLms1LnRlc3RfbWFlLAogICAgVHJhaW5fUjIgPSB4Z2IuazUudHJhaW5fcjIsIFRlc3RfUjIgPSB4Z2IuazUudGVzdF9yMgogICkpCn0KCiMgRGlzcGxheSB0aGUgbW9kZWxzIGFuZCB0aGVpciBtZXRyaWNzCnByaW50KHhnYi5rNS5tb2RlbHMpCnByaW50KHhnYi5rNS5yZXN1bHRzKQpgYGAKCgpEZWNpc2lvbiBUcmVlCmBgYHtyfQojIyMjIERlY2lzaW9uIFRyZWVzICMjIyMKCiMgRXJyb3IgaW4gdHJlZTogImZhY3RvciBwcmVkaWN0b3JzIG11c3QgaGF2ZSBhdCBtb3N0IDMyIGxldmVscyIgaXMgdGhyb3duLgojIEJhc2ljYWxseSwgaXQgYmVjb21lcyBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlIHRvIGNyZWF0ZSBzbyBtYW55IHNwbGl0cyBpbiB5b3VyIGRhdGEsIHNpbmNlIHlvdSBhcmUgc2VsZWN0aW5nIHRoZSBiZXN0IHNwbGl0IG91dCBvZiBhbGwgMl4zMiAoYXBwcm94KSBwb3NzaWJsZSBzcGxpdHMuCgoKIyBGaXQgYSBkZWNpc2lvbiB0cmVlIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBkYXRhCiN0bSA8LSB0cmVlKGludF9yYXRlIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEpCgojIE1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEKI3RtLnRyYWluX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QodG0sIG5ld2RhdGEgPSB0cmFpbl9kYXRhKQojdG0udGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHRtLCBuZXdkYXRhID0gdGVzdF9kYXRhKQoKIyBDYWxjdWxhdGUgTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwojdG0udHJhaW5fbXNlIDwtIG1lYW4oKHRtLnRyYWluX3ByZWRpY3Rpb25zIC0gdHJhaW5fZGF0YSRpbnRfcmF0ZSleMikKI3RtLnRlc3RfbXNlIDwtIG1lYW4oKHRtLnRlc3RfcHJlZGljdGlvbnMgLSB0ZXN0X2RhdGEkaW50X3JhdGUpXjIpCgojIENhbGN1bGF0ZSBSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSkgZm9yIHRyYWluaW5nIGFuZCB0ZXN0aW5nCiN0bS50cmFpbl9ybXNlIDwtIHNxcnQodG0udHJhaW5fbXNlKQojdG0udGVzdF9ybXNlIDwtIHNxcnQodG0udGVzdF9tc2UpCgojIENhbGN1bGF0ZSBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwojdG0udHJhaW5fbWFlIDwtIG1lYW4oYWJzKHRtLnRyYWluX3ByZWRpY3Rpb25zIC0gdHJhaW5fZGF0YSRpbnRfcmF0ZSkpCiN0bS50ZXN0X21hZSA8LSBtZWFuKGFicyh0bS50ZXN0X3ByZWRpY3Rpb25zIC0gdGVzdF9kYXRhJGludF9yYXRlKSkKCiMgQ2FsY3VsYXRlIFItc3F1YXJlZCAoUsKyKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKI3RtLnRyYWluX3IyIDwtIDEgLSAoc3VtKCh0cmFpbl9kYXRhJGludF9yYXRlIC0gdG0udHJhaW5fcHJlZGljdGlvbnMpXjIpIC8gc3VtKCh0cmFpbl9kYXRhJGludF9yYXRlIC0gbWVhbih0cmFpbl9kYXRhJGludF9yYXRlKSleMikpCiN0bS50ZXN0X3IyIDwtIDEgLSAoc3VtKCh0ZXN0X2RhdGEkaW50X3JhdGUgLSB0bS50ZXN0X3ByZWRpY3Rpb25zKV4yKSAvIHN1bSgodGVzdF9kYXRhJGludF9yYXRlIC0gbWVhbih0ZXN0X2RhdGEkaW50X3JhdGUpKV4yKSkKCiMgRGlzcGxheSB0aGUgbWV0cmljcwojY2F0KCJUcmFpbmluZyBNU0U6IiwgdG0udHJhaW5fbXNlLCAiXG4iKQojY2F0KCJUZXN0aW5nIE1TRToiLCB0bS50ZXN0X21zZSwgIlxuIikKI2NhdCgiVHJhaW5pbmcgUk1TRToiLCB0bS50cmFpbl9ybXNlLCAiXG4iKQojY2F0KCJUZXN0aW5nIFJNU0U6IiwgdG0udGVzdF9ybXNlLCAiXG4iKQojY2F0KCJUcmFpbmluZyBNQUU6IiwgdG0udHJhaW5fbWFlLCAiXG4iKQojY2F0KCJUZXN0aW5nIE1BRToiLCB0bS50ZXN0X21hZSwgIlxuIikKI2NhdCgiVHJhaW5pbmcgUi1zcXVhcmVkIChSwrIpOiIsIHRtLnRyYWluX3IyLCAiXG4iKQojY2F0KCJUZXN0aW5nIFItc3F1YXJlZCAoUsKyKToiLCB0bS50ZXN0X3IyLCAiXG4iKQpgYGAKClJhbmRvbSBGb3Jlc3QKYGBge3J9CiMjIyMgUmFuZG9tIEZvcmVzdCAjIyMjCgojIFRyYWluIGEgUmFuZG9tIEZvcmVzdCBtb2RlbApyZiA8LSByYW5nZXIoZm9ybXVsYSA9IGludF9yYXRlIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEsIG51bS50cmVlcyA9IDUwMCwgdmVyYm9zZT1UUlVFLCBpbXBvcnRhbmNlID0gImltcHVyaXR5Iiwgb29iLmVycm9yID0gVFJVRSkKCiMgUHJpbnQgdGhlIG1vZGVsIHN1bW1hcnkKcHJpbnQoIlJhbmRvbSBGb3Jlc3QgTW9kZWwgU3VtbWFyeToiKQpwcmludChyZikKCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YQpyZi50cmFpbl9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJmLCBkYXRhID0gdHJhaW5fZGF0YSkKcmYudGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJmLCBkYXRhID0gdGVzdF9kYXRhKQoKIyBDYWxjdWxhdGUgTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwpyZi50cmFpbl9tc2UgPC0gbWVhbigocmYudHJhaW5fcHJlZGljdGlvbnMkcHJlZGljdGlvbnMgLSB0cmFpbl9kYXRhJGludF9yYXRlKV4yKQpyZi50ZXN0X21zZSA8LSBtZWFuKChyZi50ZXN0X3ByZWRpY3Rpb25zJHByZWRpY3Rpb25zIC0gdGVzdF9kYXRhJGludF9yYXRlKV4yKQoKIyBDYWxjdWxhdGUgUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IgKFJNU0UpIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwpyZi50cmFpbl9ybXNlIDwtIHNxcnQocmYudHJhaW5fbXNlKQpyZi50ZXN0X3Jtc2UgPC0gc3FydChyZi50ZXN0X21zZSkKCiMgQ2FsY3VsYXRlIE1lYW4gQWJzb2x1dGUgRXJyb3IgKE1BRSkgZm9yIHRyYWluaW5nIGFuZCB0ZXN0aW5nCnJmLnRyYWluX21hZSA8LSBtZWFuKGFicyhyZi50cmFpbl9wcmVkaWN0aW9ucyRwcmVkaWN0aW9ucyAtIHRyYWluX2RhdGEkaW50X3JhdGUpKQpyZi50ZXN0X21hZSA8LSBtZWFuKGFicyhyZi50ZXN0X3ByZWRpY3Rpb25zJHByZWRpY3Rpb25zIC0gdGVzdF9kYXRhJGludF9yYXRlKSkKCiMgQ2FsY3VsYXRlIFItc3F1YXJlZCAoUsKyKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKcmYudHJhaW5fcjIgPC0gMSAtIChzdW0oKHRyYWluX2RhdGEkaW50X3JhdGUgLSByZi50cmFpbl9wcmVkaWN0aW9ucyRwcmVkaWN0aW9ucyleMikgLyBzdW0oKHRyYWluX2RhdGEkaW50X3JhdGUgLSBtZWFuKHRyYWluX2RhdGEkaW50X3JhdGUpKV4yKSkKcmYudGVzdF9yMiA8LSAxIC0gKHN1bSgodGVzdF9kYXRhJGludF9yYXRlIC0gcmYudGVzdF9wcmVkaWN0aW9ucyRwcmVkaWN0aW9ucyleMikgLyBzdW0oKHRlc3RfZGF0YSRpbnRfcmF0ZSAtIG1lYW4odGVzdF9kYXRhJGludF9yYXRlKSleMikpCgojIERpc3BsYXkgdGhlIG1ldHJpY3MKY2F0KCJUcmFpbmluZyBNU0U6IiwgcmYudHJhaW5fbXNlLCAiXG4iKQpjYXQoIlRlc3RpbmcgTVNFOiIsIHJmLnRlc3RfbXNlLCAiXG4iKQpjYXQoIlRyYWluaW5nIFJNU0U6IiwgcmYudHJhaW5fcm1zZSwgIlxuIikKY2F0KCJUZXN0aW5nIFJNU0U6IiwgcmYudGVzdF9ybXNlLCAiXG4iKQpjYXQoIlRyYWluaW5nIE1BRToiLCByZi50cmFpbl9tYWUsICJcbiIpCmNhdCgiVGVzdGluZyBNQUU6IiwgcmYudGVzdF9tYWUsICJcbiIpCmNhdCgiVHJhaW5pbmcgUi1zcXVhcmVkIChSwrIpOiIsIHJmLnRyYWluX3IyLCAiXG4iKQpjYXQoIlRlc3RpbmcgUi1zcXVhcmVkIChSwrIpOiIsIHJmLnRlc3RfcjIsICJcbiIpCiNyZiA8LSByYW5kb21Gb3Jlc3QoaW50X3JhdGV+LiwgZGF0YT10cmFpbl9kYXRhLCBudHJlZSA9IDUsIG10cnkgPSAzKQojYmFnLmJvc3Rvbj1yYW5kb21Gb3Jlc3QobWVkdn4uLGRhdGE9Qm9zdG9uLHN1YnNldD10cmFpbiwgbXRyeT0xMyxpbXBvcnRhbmNlID1UUlVFKQojcHJpbnQocmYpCgojIFNldCB0aGUgbnVtYmVyIG9mIGNvcmVzIHlvdSB3YW50IHRvIHVzZQojbnVtX2NvcmVzIDwtIDYgICMgQWRqdXN0IHRoaXMgbnVtYmVyIGJhc2VkIG9uIHlvdXIgc3lzdGVtJ3MgY2FwYWJpbGl0aWVzCgojIFJlZ2lzdGVyIHBhcmFsbGVsIGJhY2tlbmQKI2NsIDwtIG1ha2VDbHVzdGVyKG51bV9jb3JlcykKI3JlZ2lzdGVyRG9QYXJhbGxlbChjbCkKCiMgQXNzdW1pbmcgJ2xjX2RhdGEnIGlzIHlvdXIgZGF0YXNldAojcmZfbW9kZWwgPC0gZm9yZWFjaChudHJlZSA9IHJlcCgxMDAsIG51bV9jb3JlcyksIC5wYWNrYWdlcyA9ICdyYW5kb21Gb3Jlc3QnKSAlZG9wYXIlIHsKIyAgICByYW5kb21Gb3Jlc3QoaW50X3JhdGUgfiAuLCBkYXRhID0gbGNfZGF0YSwgbnRyZWUgPSBudHJlZSwgbXRyeSA9IHNxcnQobmNvbChsY19kYXRhKSkpCiN9CgojIEFmdGVyIHRyYWluaW5nLCBzdG9wIHRoZSBjbHVzdGVyIHRvIHJlbGVhc2UgdGhlIGNvcmVzOgojc3RvcENsdXN0ZXIoY2wpCmBgYApCb29zdGluZwpgYGB7cn0KIyMjIyBCb29zdGluZyAjIyMjCgojIERlZmluZSB0aGUgdGFyZ2V0IHZhcmlhYmxlIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwp4Z2IueV90cmFpbiA8LSB0cmFpbl9kYXRhJGludF9yYXRlCnhnYi55X3Rlc3QgPC0gdGVzdF9kYXRhJGludF9yYXRlICAjIFVzZSB0ZXN0X2RhdGEgZm9yIHRlc3RpbmcKCiMgRGVmaW5lIHRoZSBmZWF0dXJlIG1hdHJpeCBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcgKGV4Y2x1ZGUgdGhlIHRhcmdldCB2YXJpYWJsZSkKeGdiLlhfdHJhaW4gPC0gdHJhaW5fZGF0YVssIC13aGljaChuYW1lcyh0cmFpbl9kYXRhKSA9PSAnaW50X3JhdGUnKV0KeGdiLlhfdGVzdCA8LSB0ZXN0X2RhdGFbLCAtd2hpY2gobmFtZXModGVzdF9kYXRhKSA9PSAnaW50X3JhdGUnKV0gICMgVXNlIHRlc3RfZGF0YSBmb3IgdGVzdGluZwoKIyBGaXQgYSBncmFkaWVudCBib29zdGluZyByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIHhnYm9vc3QKeGdiIDwtIHhnYm9vc3QoCiAgZGF0YSA9IGFzLm1hdHJpeCh4Z2IuWF90cmFpbiksCiAgbGFiZWwgPSB4Z2IueV90cmFpbiwKICBucm91bmRzID0gMTAwLAogIHZlcmJvc2UgPSAwCikKCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YQp4Z2IudHJhaW5fcHJlZGljdGlvbnMgPC0gcHJlZGljdCh4Z2IsIG5ld2RhdGEgPSBhcy5tYXRyaXgoeGdiLlhfdHJhaW4pKQp4Z2IudGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHhnYiwgbmV3ZGF0YSA9IGFzLm1hdHJpeCh4Z2IuWF90ZXN0KSkKCiMgQ2FsY3VsYXRlIE1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKeGdiLnRyYWluX21zZSA8LSBtZWFuKCh4Z2IudHJhaW5fcHJlZGljdGlvbnMgLSB4Z2IueV90cmFpbileMikKeGdiLnRlc3RfbXNlIDwtIG1lYW4oKHhnYi50ZXN0X3ByZWRpY3Rpb25zIC0geGdiLnlfdGVzdCleMikKCiMgQ2FsY3VsYXRlIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKeGdiLnRyYWluX3Jtc2UgPC0gc3FydCh4Z2IudHJhaW5fbXNlKQp4Z2IudGVzdF9ybXNlIDwtIHNxcnQoeGdiLnRlc3RfbXNlKQoKIyBDYWxjdWxhdGUgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcKeGdiLnRyYWluX21hZSA8LSBtZWFuKGFicyh4Z2IudHJhaW5fcHJlZGljdGlvbnMgLSB4Z2IueV90cmFpbikpCnhnYi50ZXN0X21hZSA8LSBtZWFuKGFicyh4Z2IudGVzdF9wcmVkaWN0aW9ucyAtIHhnYi55X3Rlc3QpKQoKIyBDYWxjdWxhdGUgUi1zcXVhcmVkIChSwrIpIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZwp4Z2IudHJhaW5fcjIgPC0gMSAtIChzdW0oKHhnYi55X3RyYWluIC0geGdiLnRyYWluX3ByZWRpY3Rpb25zKV4yKSAvIHN1bSgoeGdiLnlfdHJhaW4gLSBtZWFuKHhnYi55X3RyYWluKSleMikpCnhnYi50ZXN0X3IyIDwtIDEgLSAoc3VtKCh4Z2IueV90ZXN0IC0geGdiLnRlc3RfcHJlZGljdGlvbnMpXjIpIC8gc3VtKCh4Z2IueV90ZXN0IC0gbWVhbih4Z2IueV90ZXN0KSleMikpCgojIERpc3BsYXkgdGhlIG1ldHJpY3MKY2F0KCJUcmFpbmluZyBNU0U6IiwgeGdiLnRyYWluX21zZSwgIlxuIikKY2F0KCJUZXN0aW5nIE1TRToiLCB4Z2IudGVzdF9tc2UsICJcbiIpCmNhdCgiVHJhaW5pbmcgUk1TRToiLCB4Z2IudHJhaW5fcm1zZSwgIlxuIikKY2F0KCJUZXN0aW5nIFJNU0U6IiwgeGdiLnRlc3Rfcm1zZSwgIlxuIikKY2F0KCJUcmFpbmluZyBNQUU6IiwgeGdiLnRyYWluX21hZSwgIlxuIikKY2F0KCJUZXN0aW5nIE1BRToiLCB4Z2IudGVzdF9tYWUsICJcbiIpCmNhdCgiVHJhaW5pbmcgUi1zcXVhcmVkIChSwrIpOiIsIHhnYi50cmFpbl9yMiwgIlxuIikKY2F0KCJUZXN0aW5nIFItc3F1YXJlZCAoUsKyKToiLCB4Z2IudGVzdF9yMiwgIlxuIikKYGBgCkZvbGxvd2luZywgYSBzY2F0dGVyIHBsb3Qgb2YgYWN0dWFsIHZzIHByZWRpY3RlZCB0cmFpbmluZyB2YWx1ZXMgZm9yIGVhY2ggbW9kZWwgaXMgcGxvdC4KVGhpcyBwbG90IGhlbHBzIHVzIHZpc3VhbGl6ZSBob3cgd2VsbCBlYWNoIG1vZGVsJ3MgcHJlZGljdGlvbnMgYWxpZ24gd2l0aCB0aGUgYWN0dWFsIGRhdGEgcG9pbnRzLgpgYGB7cn0KIyBDcmVhdGUgYSBzY2F0dGVyIHBsb3QgZnVuY3Rpb24KY3JlYXRlX3NjYXR0ZXJfcGxvdCA8LSBmdW5jdGlvbihhY3R1YWxfdmFsdWVzLCBwcmVkaWN0ZWRfdmFsdWVzLCBtb2RlbF9uYW1lKSB7CiAgbW9kZWxfY29tcGFyaXNvbl9kYXRhIDwtIGRhdGEuZnJhbWUoCiAgICBBY3R1YWwgPSBhY3R1YWxfdmFsdWVzLAogICAgUHJlZGljdGVkID0gcHJlZGljdGVkX3ZhbHVlcwogICkKICAKICBzY2F0dGVyX3Bsb3QgPC0gZ2dwbG90KG1vZGVsX2NvbXBhcmlzb25fZGF0YSwgYWVzKHggPSBBY3R1YWwsIHkgPSBQcmVkaWN0ZWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiKSArICAjIEFkZCBhIGRpYWdvbmFsIHJlZmVyZW5jZSBsaW5lCiAgICBsYWJzKHggPSAiQWN0dWFsIFRyYWluaW5nIFZhbHVlcyIsIHkgPSAiUHJlZGljdGVkIFRyYWluaW5nIFZhbHVlcyIsIHRpdGxlID0gbW9kZWxfbmFtZSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHlsaW0oLTUwLCA1MCkKICAKICByZXR1cm4oc2NhdHRlcl9wbG90KQp9CgojIENyZWF0ZSBzY2F0dGVyIHBsb3RzIGZvciBlYWNoIG1vZGVsCmxtX3NjYXR0ZXJfcGxvdCA8LSBjcmVhdGVfc2NhdHRlcl9wbG90KAogIGFjdHVhbF92YWx1ZXMgPSB0cmFpbl9kYXRhJGludF9yYXRlLAogIHByZWRpY3RlZF92YWx1ZXMgPSBsbS50cmFpbl9wcmVkaWN0aW9ucywKICBtb2RlbF9uYW1lID0gIkxpbmVhciBSZWdyZXNzaW9uIgopCgpyZl9zY2F0dGVyX3Bsb3QgPC0gY3JlYXRlX3NjYXR0ZXJfcGxvdCgKICBhY3R1YWxfdmFsdWVzID0gdHJhaW5fZGF0YSRpbnRfcmF0ZSwKICBwcmVkaWN0ZWRfdmFsdWVzID0gcmYudHJhaW5fcHJlZGljdGlvbnMkcHJlZGljdGlvbnMsCiAgbW9kZWxfbmFtZSA9ICJSYW5kb20gRm9yZXN0IgopCgp4Z2Jfc2NhdHRlcl9wbG90IDwtIGNyZWF0ZV9zY2F0dGVyX3Bsb3QoCiAgYWN0dWFsX3ZhbHVlcyA9IHhnYi55X3RyYWluLAogIHByZWRpY3RlZF92YWx1ZXMgPSB4Z2IudHJhaW5fcHJlZGljdGlvbnMsCiAgbW9kZWxfbmFtZSA9ICJYR0Jvb3N0IgopCgojIERpc3BsYXkgdGhlIHNjYXR0ZXIgcGxvdHMgc2VwYXJhdGVseQpwcmludChsbV9zY2F0dGVyX3Bsb3QpCnByaW50KHJmX3NjYXR0ZXJfcGxvdCkKcHJpbnQoeGdiX3NjYXR0ZXJfcGxvdCkKYGBgCkZvbGxvd2luZywgYSBzY2F0dGVyIHBsb3Qgb2YgYWN0dWFsIHZzIHByZWRpY3RlZCB0ZXN0aW5nIHZhbHVlcyBmb3IgZWFjaCBtb2RlbCBpcyBwbG90LgpUaGlzIHBsb3QgaGVscHMgdXMgdmlzdWFsaXplIGhvdyB3ZWxsIGVhY2ggbW9kZWwncyBwcmVkaWN0aW9ucyBhbGlnbiB3aXRoIHRoZSBhY3R1YWwgZGF0YSBwb2ludHMuCmBgYHtyfQojIENyZWF0ZSBhIHNjYXR0ZXIgcGxvdCBmdW5jdGlvbgpjcmVhdGVfc2NhdHRlcl9wbG90IDwtIGZ1bmN0aW9uKGFjdHVhbF92YWx1ZXMsIHByZWRpY3RlZF92YWx1ZXMsIG1vZGVsX25hbWUpIHsKICBtb2RlbF9jb21wYXJpc29uX2RhdGEgPC0gZGF0YS5mcmFtZSgKICAgIEFjdHVhbCA9IGFjdHVhbF92YWx1ZXMsCiAgICBQcmVkaWN0ZWQgPSBwcmVkaWN0ZWRfdmFsdWVzCiAgKQogIAogIHNjYXR0ZXJfcGxvdCA8LSBnZ3Bsb3QobW9kZWxfY29tcGFyaXNvbl9kYXRhLCBhZXMoeCA9IEFjdHVhbCwgeSA9IFByZWRpY3RlZCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsgICMgQWRkIGEgZGlhZ29uYWwgcmVmZXJlbmNlIGxpbmUKICAgIGxhYnMoeCA9ICJBY3R1YWwgVGVzdGluZyBWYWx1ZXMiLCB5ID0gIlByZWRpY3RlZCBUZXN0aW5nIFZhbHVlcyIsIHRpdGxlID0gbW9kZWxfbmFtZSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHlsaW0oLTUwLCA1MCkgKwogICAgeGxpbSgwLCA0MCkKICAKICByZXR1cm4oc2NhdHRlcl9wbG90KQp9CgojIENyZWF0ZSBzY2F0dGVyIHBsb3RzIGZvciBlYWNoIG1vZGVsCmxtX3NjYXR0ZXJfcGxvdCA8LSBjcmVhdGVfc2NhdHRlcl9wbG90KAogIGFjdHVhbF92YWx1ZXMgPSB0ZXN0X2RhdGEkaW50X3JhdGUsCiAgcHJlZGljdGVkX3ZhbHVlcyA9IGxtLnRlc3RfcHJlZGljdGlvbnMsCiAgbW9kZWxfbmFtZSA9ICJMaW5lYXIgUmVncmVzc2lvbiIKKQoKcmZfc2NhdHRlcl9wbG90IDwtIGNyZWF0ZV9zY2F0dGVyX3Bsb3QoCiAgYWN0dWFsX3ZhbHVlcyA9IHRlc3RfZGF0YSRpbnRfcmF0ZSwKICBwcmVkaWN0ZWRfdmFsdWVzID0gcmYudGVzdF9wcmVkaWN0aW9ucyRwcmVkaWN0aW9ucywKICBtb2RlbF9uYW1lID0gIlJhbmRvbSBGb3Jlc3QiCikKCnhnYl9zY2F0dGVyX3Bsb3QgPC0gY3JlYXRlX3NjYXR0ZXJfcGxvdCgKICBhY3R1YWxfdmFsdWVzID0geGdiLnlfdGVzdCwKICBwcmVkaWN0ZWRfdmFsdWVzID0geGdiLnRlc3RfcHJlZGljdGlvbnMsCiAgbW9kZWxfbmFtZSA9ICJYR0Jvb3N0IgopCgojIERpc3BsYXkgdGhlIHNjYXR0ZXIgcGxvdHMgc2VwYXJhdGVseQpwcmludChsbV9zY2F0dGVyX3Bsb3QpCnByaW50KHJmX3NjYXR0ZXJfcGxvdCkKcHJpbnQoeGdiX3NjYXR0ZXJfcGxvdCkKYGBgCgpSZXNpZHVhbCBwbG90cyBjYW4gaGVscCBpZGVudGlmeSBwYXR0ZXJucyBpbiBwcmVkaWN0aW9uIGVycm9ycyBhbmQgYXNzZXNzIHdoZXRoZXIgdGhlIGFzc3VtcHRpb25zIG9mIGxpbmVhciByZWdyZXNzaW9uIChpZiBhcHBsaWNhYmxlKSBhcmUgbWV0LgpgYGB7cn0KIyBDcmVhdGUgYSByZXNpZHVhbCBwbG90IGZ1bmN0aW9uCmNyZWF0ZV9yZXNpZHVhbF9wbG90IDwtIGZ1bmN0aW9uKGFjdHVhbF92YWx1ZXMsIHByZWRpY3RlZF92YWx1ZXMsIG1vZGVsX25hbWUpIHsKICByZXNpZHVhbHMgPC0gYWN0dWFsX3ZhbHVlcyAtIHByZWRpY3RlZF92YWx1ZXMKICByZXNpZHVhbF9kYXRhIDwtIGRhdGEuZnJhbWUoCiAgICBQcmVkaWN0ZWQgPSBwcmVkaWN0ZWRfdmFsdWVzLAogICAgUmVzaWR1YWxzID0gcmVzaWR1YWxzCiAgKQogIAogIHJlc2lkdWFsX3Bsb3QgPC0gZ2dwbG90KHJlc2lkdWFsX2RhdGEsIGFlcyh4ID0gUHJlZGljdGVkLCB5ID0gUmVzaWR1YWxzKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsgICMgUmVkIGhvcml6b250YWwgcmVmZXJlbmNlIGxpbmUKICAgIGxhYnMoeCA9ICJQcmVkaWN0ZWQgVmFsdWVzIiwgeSA9ICJSZXNpZHVhbHMiLCB0aXRsZSA9IHBhc3RlKCJSZXNpZHVhbCBQbG90IC0iLCBtb2RlbF9uYW1lKSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHlsaW0oLTMwLCAzMCkgKwogICAgeGxpbSgwLCA0MCkKICAKICByZXR1cm4ocmVzaWR1YWxfcGxvdCkKfQoKIyBDcmVhdGUgcmVzaWR1YWwgcGxvdHMgZm9yIGVhY2ggbW9kZWwKbG1fcmVzaWR1YWxfcGxvdCA8LSBjcmVhdGVfcmVzaWR1YWxfcGxvdCgKICBhY3R1YWxfdmFsdWVzID0gdHJhaW5fZGF0YSRpbnRfcmF0ZSwKICBwcmVkaWN0ZWRfdmFsdWVzID0gbG0udHJhaW5fcHJlZGljdGlvbnMsCiAgbW9kZWxfbmFtZSA9ICJMaW5lYXIgUmVncmVzc2lvbiIKKQoKcmZfcmVzaWR1YWxfcGxvdCA8LSBjcmVhdGVfcmVzaWR1YWxfcGxvdCgKICBhY3R1YWxfdmFsdWVzID0gdHJhaW5fZGF0YSRpbnRfcmF0ZSwKICBwcmVkaWN0ZWRfdmFsdWVzID0gcmYudHJhaW5fcHJlZGljdGlvbnMkcHJlZGljdGlvbnMsCiAgbW9kZWxfbmFtZSA9ICJSYW5kb20gRm9yZXN0IgopCgp4Z2JfcmVzaWR1YWxfcGxvdCA8LSBjcmVhdGVfcmVzaWR1YWxfcGxvdCgKICBhY3R1YWxfdmFsdWVzID0geGdiLnlfdHJhaW4sCiAgcHJlZGljdGVkX3ZhbHVlcyA9IHhnYi50cmFpbl9wcmVkaWN0aW9ucywKICBtb2RlbF9uYW1lID0gIlhHQm9vc3QiCikKCiMgRGlzcGxheSB0aGUgcmVzaWR1YWwgcGxvdHMgc2VwYXJhdGVseQpwcmludChsbV9yZXNpZHVhbF9wbG90KQpwcmludChyZl9yZXNpZHVhbF9wbG90KQpwcmludCh4Z2JfcmVzaWR1YWxfcGxvdCkKYGBgCkZyb20gdGhlIHBsb3RzIGFib3ZlIHdlIGNhbiBjbGVhcmx5IHNlZSB0aGF0OgoKLSAgIExpbmVhciBSZWdyZXNzaW9uIFJlc2lkdWFsIFBsb3Q6CiAgICAtICAgUGF0dGVybjogVGhlcmUgYXBwZWFycyB0byBiZSBhIGNsZWFyIHBhdHRlcm4gaW4gdGhlIHJlc2lkdWFscywgd2l0aCBhIGZ1bm5lbCBzaGFwZSB0aGF0IHdpZGVucyBhcyB0aGUgcHJlZGljdGVkIHZhbHVlcyBpbmNyZWFzZS4gVGhpcyBzdWdnZXN0cyBoZXRlcm9zY2VkYXN0aWNpdHksIHdoZXJlIHRoZSB2YXJpYW5jZSBvZiB0aGUgcmVzaWR1YWxzIGlzIG5vdCBjb25zdGFudCBhY3Jvc3MgdGhlIHJhbmdlIG9mIHByZWRpY3RlZCB2YWx1ZXMuCiAgICAtICAgT3V0bGllcnM6IFRoZXJlIGFyZSBzZXZlcmFsIHByb21pbmVudCBvdXRsaWVycywgcGFydGljdWxhcmx5IGZvciBoaWdoZXIgcHJlZGljdGVkIHZhbHVlcy4gVGhlc2UgcG9pbnRzIHNpZ25pZmljYW50bHkgZGV2aWF0ZSBmcm9tIHRoZSByZWQgZGFzaGVkIGxpbmUsIHdoaWNoIHJlcHJlc2VudHMgemVybyByZXNpZHVhbHMuCi0gICBSYW5kb20gRm9yZXN0IFJlc2lkdWFsIFBsb3Q6CiAgICAtICAgUGF0dGVybjogVGhlIHJlc2lkdWFscyBzZWVtIHRvIGJlIHJhbmRvbWx5IGRpc3BlcnNlZCBhcm91bmQgdGhlIHplcm8gbGluZSBhdCBsb3dlciBwcmVkaWN0ZWQgdmFsdWVzLCB3aGljaCBpcyBhIGdvb2Qgc2lnbi4gSG93ZXZlciwgYXMgcHJlZGljdGVkIHZhbHVlcyBpbmNyZWFzZSwgdGhlIHJlc2lkdWFscyBhbHNvIGluY3JlYXNlLCBzdWdnZXN0aW5nIGEgc3lzdGVtYXRpYyBlcnJvciBpbiB0aGUgbW9kZWwgcHJlZGljdGlvbnMuCiAgICAtICAgQ29uY2VudHJhdGlvbjogVGhlcmUgaXMgYSBoaWdoZXIgY29uY2VudHJhdGlvbiBvZiByZXNpZHVhbHMgYXJvdW5kIHRoZSB6ZXJvIGxpbmUgY29tcGFyZWQgdG8gdGhlIExpbmVhciBSZWdyZXNzaW9uIG1vZGVsLCB3aGljaCBjb3VsZCBpbmRpY2F0ZSBhIGJldHRlciBmaXQKLSAgICBYR0Jvb3N0IFJlc2lkdWFsIFBsb3Q6CiAgICAtICAgUGF0dGVybjogVGhlIHJlc2lkdWFscyBpbiB0aGlzIHBsb3QgYXJlIG1vcmUgZXZlbmx5IGRpc3BlcnNlZCBhY3Jvc3MgdGhlIHByZWRpY3RlZCB2YWx1ZXMgdGhhbiBpbiB0aGUgTGluZWFyIFJlZ3Jlc3Npb24gcGxvdCwgc3VnZ2VzdGluZyBhIG1vcmUgY29uc2lzdGVudCB2YXJpYW5jZSAoaG9tb3NjZWRhc3RpY2l0eSkgYW5kIHBvdGVudGlhbGx5IGEgYmV0dGVyIGZpdC4KICAgIC0gICBPdXRsaWVyczogVGhpcyBwbG90IGFsc28gc2hvd3Mgb3V0bGllcnMsIGJ1dCB0aGV5IGRvIG5vdCBhcHBlYXIgYXMgZXh0cmVtZSBhcyBpbiB0aGUgTGluZWFyIFJlZ3Jlc3Npb24gcGxvdC4gVGhlIHNwcmVhZCBvZiByZXNpZHVhbHMgaXMgbmFycm93ZXIgY29tcGFyZWQgdG8gdGhlIFJhbmRvbSBGb3Jlc3QgcGxvdC4KLSAgIENvbXBhcmlzb246CiAgICAtICAgSGV0ZXJvc2NlZGFzdGljaXR5OiBUaGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwgZXhoaWJpdHMgY2xlYXIgaGV0ZXJvc2NlZGFzdGljaXR5LCB3aGljaCBpcyBhIHNpZ24gb2YgbW9kZWwgaW5hZGVxdWFjeS4gVGhpcyBjb3VsZCBiZSBpbXByb3ZlZCB3aXRoIHRyYW5zZm9ybWF0aW9ucyBvciB1c2luZyBtb2RlbHMgdGhhdCBoYW5kbGUgbm9uLWNvbnN0YW50IHZhcmlhbmNlIGJldHRlci4KICAgIC0gICBNb2RlbCBGaXQ6IFRoZSBSYW5kb20gRm9yZXN0IG1vZGVsIHNlZW1zIHRvIGZpdCB0aGUgbG93ZXIgcmFuZ2Ugb2YgcHJlZGljdGVkIHZhbHVlcyB3ZWxsIGJ1dCBzaG93cyBpbmNyZWFzaW5nIHJlc2lkdWFscyB3aXRoIGhpZ2hlciBwcmVkaWN0ZWQgdmFsdWVzLCB3aGljaCBjb3VsZCBpbmRpY2F0ZSBvdmVyZml0dGluZyBvciBhIGxhY2sgb2YgZ2VuZXJhbGl6YXRpb24gZm9yIGhpZ2hlciB2YWx1ZXMuCiAgICAtICAgQ29uc2lzdGVuY3k6IFRoZSBYR0Jvb3N0IG1vZGVsIHNlZW1zIHRvIHNob3cgYSBtb3JlIGNvbnNpc3RlbnQgc3ByZWFkIG9mIHJlc2lkdWFscyBhcm91bmQgdGhlIHplcm8gbGluZSwgd2hpY2ggaXMgaW5kaWNhdGl2ZSBvZiBhIG1vZGVsIHRoYXQgaGFzIGEgY29uc2lzdGVudCBwZXJmb3JtYW5jZSBhY3Jvc3MgdGhlIHJhbmdlIG9mIHByZWRpY3RlZCB2YWx1ZXMuCiAgICAtICAgT3V0bGllcnM6IEFsbCB0aHJlZSBtb2RlbHMgaGF2ZSBvdXRsaWVycywgYnV0IHRoZWlyIGltcGFjdCBzZWVtcyBtb3N0IHByb25vdW5jZWQgaW4gdGhlIExpbmVhciBSZWdyZXNzaW9uIG1vZGVsLgoKVGhpcyB2aXN1YWxpemF0aW9uIGNhbiBoZWxwIHlvdSBjb21wYXJlIHRoZSBkaXN0cmlidXRpb24gb2YgcHJlZGljdGlvbiBlcnJvcnMgYWNyb3NzIG1vZGVscy4KYGBge3J9CiMgQ3JlYXRlIGEgZGVuc2l0eSBwbG90IGZ1bmN0aW9uIGZvciByZXNpZHVhbHMKY3JlYXRlX3Jlc2lkdWFsX2RlbnNpdHlfcGxvdCA8LSBmdW5jdGlvbihhY3R1YWxfdmFsdWVzLCBwcmVkaWN0ZWRfdmFsdWVzLCBtb2RlbF9uYW1lKSB7CiAgcmVzaWR1YWxzIDwtIGFjdHVhbF92YWx1ZXMgLSBwcmVkaWN0ZWRfdmFsdWVzCiAgcmVzaWR1YWxfZGF0YSA8LSBkYXRhLmZyYW1lKFJlc2lkdWFscyA9IHJlc2lkdWFscykKICAKICBkZW5zaXR5X3Bsb3QgPC0gZ2dwbG90KHJlc2lkdWFsX2RhdGEsIGFlcyh4ID0gUmVzaWR1YWxzKSkgKwogICAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAic2t5Ymx1ZSIsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjcpICsKICAgIGxhYnMoeCA9ICJSZXNpZHVhbHMiLCB5ID0gIkRlbnNpdHkiLCB0aXRsZSA9IHBhc3RlKCJSZXNpZHVhbCBEZW5zaXR5IFBsb3QgLSIsIG1vZGVsX25hbWUpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgeGxpbSgtMzAsMzApICsgCiAgICB5bGltKDAsIDAuMzUpCiAgICAKICAKICByZXR1cm4oZGVuc2l0eV9wbG90KQp9CgojIENyZWF0ZSBkZW5zaXR5IHBsb3RzIGZvciByZXNpZHVhbHMgZm9yIGVhY2ggbW9kZWwKbG1fcmVzaWR1YWxfZGVuc2l0eV9wbG90IDwtIGNyZWF0ZV9yZXNpZHVhbF9kZW5zaXR5X3Bsb3QoCiAgYWN0dWFsX3ZhbHVlcyA9IHRyYWluX2RhdGEkaW50X3JhdGUsCiAgcHJlZGljdGVkX3ZhbHVlcyA9IGxtLnRyYWluX3ByZWRpY3Rpb25zLAogIG1vZGVsX25hbWUgPSAiTGluZWFyIFJlZ3Jlc3Npb24iCikKCnJmX3Jlc2lkdWFsX2RlbnNpdHlfcGxvdCA8LSBjcmVhdGVfcmVzaWR1YWxfZGVuc2l0eV9wbG90KAogIGFjdHVhbF92YWx1ZXMgPSB0cmFpbl9kYXRhJGludF9yYXRlLAogIHByZWRpY3RlZF92YWx1ZXMgPSByZi50cmFpbl9wcmVkaWN0aW9ucyRwcmVkaWN0aW9ucywKICBtb2RlbF9uYW1lID0gIlJhbmRvbSBGb3Jlc3QiCikKCnhnYl9yZXNpZHVhbF9kZW5zaXR5X3Bsb3QgPC0gY3JlYXRlX3Jlc2lkdWFsX2RlbnNpdHlfcGxvdCgKICBhY3R1YWxfdmFsdWVzID0geGdiLnlfdHJhaW4sCiAgcHJlZGljdGVkX3ZhbHVlcyA9IHhnYi50cmFpbl9wcmVkaWN0aW9ucywKICBtb2RlbF9uYW1lID0gIlhHQm9vc3QiCikKCiMgRGlzcGxheSB0aGUgZGVuc2l0eSBwbG90cyBzZXBhcmF0ZWx5CnByaW50KGxtX3Jlc2lkdWFsX2RlbnNpdHlfcGxvdCkKcHJpbnQocmZfcmVzaWR1YWxfZGVuc2l0eV9wbG90KQpwcmludCh4Z2JfcmVzaWR1YWxfZGVuc2l0eV9wbG90KQpgYGAKClRoaXMgdmlzdWFsaXphdGlvbiBjYW4gaGVscCB5b3UgY29tcGFyZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHByZWRpY3Rpb24gZXJyb3JzIGFjcm9zcyBtb2RlbHMgdGhyb3VnaCBoaXN0b2dyYW1zLgoKYGBge3J9CiMgQ3JlYXRlIGEgaGlzdG9ncmFtIHBsb3QgZnVuY3Rpb24gZm9yIHJlc2lkdWFscyB3aXRoIGEgcmVkIGRlbnNpdHkgY3VydmUKY3JlYXRlX3Jlc2lkdWFsX2hpc3RvZ3JhbV9wbG90IDwtIGZ1bmN0aW9uKGFjdHVhbF92YWx1ZXMsIHByZWRpY3RlZF92YWx1ZXMsIG1vZGVsX25hbWUpIHsKICByZXNpZHVhbHMgPC0gYWN0dWFsX3ZhbHVlcyAtIHByZWRpY3RlZF92YWx1ZXMKICByZXNpZHVhbF9kYXRhIDwtIGRhdGEuZnJhbWUoUmVzaWR1YWxzID0gcmVzaWR1YWxzKQogIAogIGhpc3RvZ3JhbV9wbG90IDwtIGdncGxvdChyZXNpZHVhbF9kYXRhLCBhZXMoeCA9IFJlc2lkdWFscykpICsKICAgIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gYWZ0ZXJfc3RhdChkZW5zaXR5KSksIGJpbnMgPSAzMCwgZmlsbCA9ICJza3libHVlIiwgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuNykgKyAgIyBVc2UgZGVuc2l0eSBvbiB0aGUgeS1heGlzIGZvciB0aGUgaGlzdG9ncmFtCiAgICBnZW9tX2RlbnNpdHkoY29sb3IgPSAicmVkIiwgbGluZXdpZHRoID0gMS41KSArICAjIEFkZCB0aGUgZGVuc2l0eSBwbG90IGluIHJlZAogICAgbGFicyh4ID0gIlJlc2lkdWFscyIsIHkgPSAiRGVuc2l0eSIsIHRpdGxlID0gcGFzdGUoIlJlc2lkdWFsIEhpc3RvZ3JhbSBQbG90IHdpdGggRGVuc2l0eSBDdXJ2ZSAtIiwgbW9kZWxfbmFtZSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB4bGltKC0yMCwyMCkgKyAKICAgIHlsaW0oMCwgMC4zKQogIAogIHJldHVybihoaXN0b2dyYW1fcGxvdCkKfQoKIyBDcmVhdGUgaGlzdG9ncmFtIHBsb3RzIGZvciByZXNpZHVhbHMgZm9yIGVhY2ggbW9kZWwKbG1fcmVzaWR1YWxfaGlzdG9ncmFtX3Bsb3QgPC0gY3JlYXRlX3Jlc2lkdWFsX2hpc3RvZ3JhbV9wbG90KAogIGFjdHVhbF92YWx1ZXMgPSB0cmFpbl9kYXRhJGludF9yYXRlLAogIHByZWRpY3RlZF92YWx1ZXMgPSBsbS50cmFpbl9wcmVkaWN0aW9ucywKICBtb2RlbF9uYW1lID0gIkxpbmVhciBSZWdyZXNzaW9uIgopCgpyZl9yZXNpZHVhbF9oaXN0b2dyYW1fcGxvdCA8LSBjcmVhdGVfcmVzaWR1YWxfaGlzdG9ncmFtX3Bsb3QoCiAgYWN0dWFsX3ZhbHVlcyA9IHRyYWluX2RhdGEkaW50X3JhdGUsCiAgcHJlZGljdGVkX3ZhbHVlcyA9IHJmLnRyYWluX3ByZWRpY3Rpb25zJHByZWRpY3Rpb25zLAogIG1vZGVsX25hbWUgPSAiUmFuZG9tIEZvcmVzdCIKKQoKeGdiX3Jlc2lkdWFsX2hpc3RvZ3JhbV9wbG90IDwtIGNyZWF0ZV9yZXNpZHVhbF9oaXN0b2dyYW1fcGxvdCgKICBhY3R1YWxfdmFsdWVzID0geGdiLnlfdHJhaW4sCiAgcHJlZGljdGVkX3ZhbHVlcyA9IHhnYi50cmFpbl9wcmVkaWN0aW9ucywKICBtb2RlbF9uYW1lID0gIlhHQm9vc3QiCikKCiMgRGlzcGxheSB0aGUgaGlzdG9ncmFtIHBsb3RzIHNlcGFyYXRlbHkKcHJpbnQobG1fcmVzaWR1YWxfaGlzdG9ncmFtX3Bsb3QpCnByaW50KHJmX3Jlc2lkdWFsX2hpc3RvZ3JhbV9wbG90KQpwcmludCh4Z2JfcmVzaWR1YWxfaGlzdG9ncmFtX3Bsb3QpCmBgYAoKRm9yIGVhY2ggbW9kZWwgYSBiYXIgY2hhcnQgdGhhdCBkaXNwbGF5cyB0aGUgUi1zcXVhcmVkIChjb2VmZmljaWVudCBvZiBkZXRlcm1pbmF0aW9uKSB2YWx1ZXMgaXMgY3JlYXRlZC4KUi1zcXVhcmVkIG1lYXN1cmVzIHRoZSBwcm9wb3J0aW9uIG9mIHZhcmlhbmNlIGluIHRoZSB0YXJnZXQgdmFyaWFibGUgZXhwbGFpbmVkIGJ5IHRoZSBtb2RlbC4gSGlnaGVyIFItc3F1YXJlZCB2YWx1ZXMgaW5kaWNhdGUgYmV0dGVyIG1vZGVsIGZpdC4KYGBge3J9CiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIFItc3F1YXJlZCB2YWx1ZXMgZm9yIGVhY2ggbW9kZWwKbW9kZWxfbmFtZXMgPC0gYygiTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IikKcl9zcXVhcmVkX3ZhbHVlc190cmFpbiA8LSBjKAogIGxtLnRyYWluX3IyLAogIHJmLnRyYWluX3IyLAogIHhnYi50cmFpbl9yMgopCnJfc3F1YXJlZF92YWx1ZXNfdGVzdCA8LSBjKAogIGxtLnRlc3RfcjIsCiAgcmYudGVzdF9yMiwKICB4Z2IudGVzdF9yMgopCgpyX3NxdWFyZWRfZGF0YV90cmFpbiA8LSBkYXRhLmZyYW1lKE1vZGVsID0gZmFjdG9yKG1vZGVsX25hbWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUl9zcXVhcmVkID0gcl9zcXVhcmVkX3ZhbHVlc190cmFpbikKcl9zcXVhcmVkX2RhdGFfdGVzdCA8LSBkYXRhLmZyYW1lKE1vZGVsID0gZmFjdG9yKG1vZGVsX25hbWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUl9zcXVhcmVkID0gcl9zcXVhcmVkX3ZhbHVlc190ZXN0KQoKIyBDcmVhdGUgdGhlIFItc3F1YXJlZCBjb21wYXJpc29uIGJhciBjaGFydApyX3NxdWFyZWRfYmFyX2NoYXJ0X3RyYWluIDwtIGdncGxvdChyX3NxdWFyZWRfZGF0YV90cmFpbiwgYWVzKHggPSBNb2RlbCwgeSA9IFJfc3F1YXJlZCwgZmlsbCA9IE1vZGVsKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgbGFicyh4ID0gIk1vZGVsIiwgeSA9ICJSLXNxdWFyZWQgKFLCsikiLCB0aXRsZSA9ICJSLXNxdWFyZWQgQ29tcGFyaXNvbiBUcmFpbmluZyIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgeWxpbSgwLDEpCnJfc3F1YXJlZF9iYXJfY2hhcnRfdGVzdCA8LSBnZ3Bsb3Qocl9zcXVhcmVkX2RhdGFfdGVzdCwgYWVzKHggPSBNb2RlbCwgeSA9IFJfc3F1YXJlZCwgZmlsbCA9IE1vZGVsKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgbGFicyh4ID0gIk1vZGVsIiwgeSA9ICJSLXNxdWFyZWQgKFLCsikiLCB0aXRsZSA9ICJSLXNxdWFyZWQgQ29tcGFyaXNvbiBUZXN0aW5nIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB5bGltKDAsMSkKCiMgRGlzcGxheSB0aGUgUi1zcXVhcmVkIGNvbXBhcmlzb24gYmFyIGNoYXJ0CnByaW50KHJfc3F1YXJlZF9iYXJfY2hhcnRfdHJhaW4pCnByaW50KHJfc3F1YXJlZF9iYXJfY2hhcnRfdGVzdCkKYGBgCkEgYmFyIGNoYXJ0IHRoYXQgY29tcGFyZXMgdGhlIE1BRSBvciBSTVNFIHZhbHVlcywgaXMgZ2VuZXJhdGVkIGZvciBlYWNoIG1vZGVsLgpUaGVzZSBtZXRyaWNzIHF1YW50aWZ5IHRoZSBhdmVyYWdlIHByZWRpY3Rpb24gZXJyb3JzIG9mIGVhY2ggbW9kZWwsIGFuZCBsb3dlciB2YWx1ZXMgYXJlIHByZWZlcnJlZC4KYGBge3J9CiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIE1BRSBhbmQgUk1TRSB2YWx1ZXMgZm9yIGVhY2ggbW9kZWwKbW9kZWxfbmFtZXMgPC0gYygiTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiwiTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IikKZXJyb3JfdmFsdWVzX3RyYWluIDwtIGMoCiAgbG0udHJhaW5fbWFlLAogIHJmLnRyYWluX21hZSwKICB4Z2IudHJhaW5fbWFlLAogIGxtLnRyYWluX3Jtc2UsCiAgcmYudHJhaW5fcm1zZSwKICB4Z2IudHJhaW5fcm1zZQopCmVycm9yX3ZhbHVlc190ZXN0IDwtIGMoCiAgbG0udGVzdF9tYWUsCiAgcmYudGVzdF9tYWUsCiAgeGdiLnRlc3RfbWFlLAogIGxtLnRlc3Rfcm1zZSwKICByZi50ZXN0X3Jtc2UsCiAgeGdiLnRlc3Rfcm1zZQopCmVycm9yX3R5cGUgPC0gYygKICAiTUFFIiwgIk1BRSIsICJNQUUiLCJSTVNFIiwiUk1TRSIsIlJNU0UiCikKbW9kZWxfZXJyb3JzX3RyYWluIDwtIGRhdGEuZnJhbWUoTW9kZWwgPSBmYWN0b3IobW9kZWxfbmFtZXMsIGxldmVscyA9IGMoIkxpbmVhciBSZWdyZXNzaW9uIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpKSwKICAgICAgICAgICAgICAgIEVycm9yID0gZXJyb3JfdmFsdWVzX3RyYWluLCBUeXBlID0gZXJyb3JfdHlwZSkKbW9kZWxfZXJyb3JzX3Rlc3QgPC0gZGF0YS5mcmFtZShNb2RlbCA9IGZhY3Rvcihtb2RlbF9uYW1lcywgbGV2ZWxzID0gYygiTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IikpLAogICAgICAgICAgICAgICAgRXJyb3IgPSBlcnJvcl92YWx1ZXNfdGVzdCwgVHlwZSA9IGVycm9yX3R5cGUpCiMgQ3JlYXRlIHRoZSBNQUUgb3IgUk1TRSBjb21wYXJpc29uIGJhciBjaGFydAplcnJvcl9iYXJfY2hhcnRfdHJhaW4gPC0gZ2dwbG90KG1vZGVsX2Vycm9yc190cmFpbiwgYWVzKHggPSBNb2RlbCwgeSA9IEVycm9yLCBmaWxsID0gVHlwZSkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgbGFicyh4ID0gIk1vZGVsIiwgeSA9ICJFcnJvciBWYWx1ZSIsIHRpdGxlID0gIlRyYWluaW5nIE1BRSBhbmQgUk1TRSBDb21wYXJpc29uIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB5bGltKDAsIDQpCgplcnJvcl9iYXJfY2hhcnRfdGVzdCA8LSBnZ3Bsb3QobW9kZWxfZXJyb3JzX3Rlc3QsIGFlcyh4ID0gTW9kZWwsIHkgPSBFcnJvciwgZmlsbCA9IFR5cGUpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGxhYnMoeCA9ICJNb2RlbCIsIHkgPSAiRXJyb3IgVmFsdWUiLCB0aXRsZSA9ICJUZXN0aW5nIE1BRSBhbmQgUk1TRSBDb21wYXJpc29uIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB5bGltKDAsIDQpCgojIERpc3BsYXkgdGhlIE1BRSBhbmQgUk1TRSBjb21wYXJpc29uIGJhciBjaGFydApwcmludChlcnJvcl9iYXJfY2hhcnRfdHJhaW4pCnByaW50KGVycm9yX2Jhcl9jaGFydF90ZXN0KQpgYGAKCgpgYGB7cn0KIyMjIyBSYW5kb20gRm9yZXN0IEZlYXR1cmUgSW1wb3J0YW5jZSBQbG90ICMjIyMKdjEgPC0gdmlwKHJmLCB0aXRsZSA9ICJSYW5nZXIiLCBudW1fZmVhdHVyZXMgPSAyMCkgCnBsb3QodjEpCmBgYAoKCkxlYXJuaW5nIGN1cnZlIHVzaW5nIFJNU0UgYW5kIFJeMjoKYGBge3J9CiMgVE9ETzogY2hhbmdlIHRoZSB4LWF4ZXMKIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggUk1TRSBhbmQgUi1zcXVhcmVkIHZhbHVlcyBmb3IgZWFjaCBtb2RlbCBhbmQgc2FtcGxlIHNpemUKbW9kZWxfbmFtZXMgPC0gYygiTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IikKc2FtcGxlX3NpemVzIDwtIHNlcSgxMCwgbnJvdyh0cmFpbl9kYXRhKSwgYnkgPSAxMCkgICMgQWRqdXN0IHRoZSBzYW1wbGUgc2l6ZXMgYXMgbmVlZGVkCgojIENyZWF0ZSBkYXRhIGZyYW1lcyB3aXRoIFJNU0UgYW5kIFItc3F1YXJlZCB2YWx1ZXMgZm9yIGVhY2ggbW9kZWwKcm1zZV9kYXRhIDwtIGRhdGEuZnJhbWUoCiAgTW9kZWwgPSByZXAobW9kZWxfbmFtZXMsIGVhY2ggPSBsZW5ndGgoc2FtcGxlX3NpemVzKSksCiAgU2FtcGxlX1NpemUgPSByZXAoc2FtcGxlX3NpemVzLCB0aW1lcyA9IGxlbmd0aChtb2RlbF9uYW1lcykpLAogIFJNU0UgPSBjKAogICAgbG0udHJhaW5fcm1zZSwgcmYudHJhaW5fcm1zZSwgeGdiLnRyYWluX3Jtc2UKICApCikKCnJfc3F1YXJlZF9kYXRhIDwtIGRhdGEuZnJhbWUoCiAgTW9kZWwgPSByZXAobW9kZWxfbmFtZXMsIGVhY2ggPSBsZW5ndGgoc2FtcGxlX3NpemVzKSksCiAgU2FtcGxlX1NpemUgPSByZXAoc2FtcGxlX3NpemVzLCB0aW1lcyA9IGxlbmd0aChtb2RlbF9uYW1lcykpLAogIFJfc3F1YXJlZCA9IGMoCiAgICBsbS50cmFpbl9yMiwgcmYudHJhaW5fcjIsIHhnYi50cmFpbl9yMgogICkKKQoKIyBDcmVhdGUgUk1TRSBsZWFybmluZyBjdXJ2ZQpybXNlX2N1cnZlIDwtIGdncGxvdChybXNlX2RhdGEsIGFlcyh4ID0gU2FtcGxlX1NpemUsIHkgPSBSTVNFLCBjb2xvciA9IE1vZGVsKSkgKwogIGdlb21fbGluZSgpICsKICBsYWJzKHggPSAiU2FtcGxlIFNpemUiLCB5ID0gIlJNU0UiLCB0aXRsZSA9ICJSTVNFIExlYXJuaW5nIEN1cnZlIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBDcmVhdGUgUi1zcXVhcmVkIGxlYXJuaW5nIGN1cnZlCnJfc3F1YXJlZF9jdXJ2ZSA8LSBnZ3Bsb3Qocl9zcXVhcmVkX2RhdGEsIGFlcyh4ID0gU2FtcGxlX1NpemUsIHkgPSBSX3NxdWFyZWQsIGNvbG9yID0gTW9kZWwpKSArCiAgZ2VvbV9saW5lKCkgKwogIGxhYnMoeCA9ICJTYW1wbGUgU2l6ZSIsIHkgPSAiUi1zcXVhcmVkIiwgdGl0bGUgPSAiUi1zcXVhcmVkIExlYXJuaW5nIEN1cnZlIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBEaXNwbGF5IHRoZSBSTVNFIGFuZCBSLXNxdWFyZWQgbGVhcm5pbmcgY3VydmVzCnByaW50KHJtc2VfY3VydmUpCnByaW50KHJfc3F1YXJlZF9jdXJ2ZSkKYGBgCgoKCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4KCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ3RybCtTaGlmdCtLKiB0byBwcmV2aWV3IHRoZSBIVE1MIGZpbGUpLgoKVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLgo=